This one uses POSIX capabilities to drop all root privs except for
CAP_SYS_NICE, therefore, this is reasonably secure.

There is one catch. For some reason a suid root app cannot read
/proc/self/exe so relocatability isn't used, and anyway it'd be
insecure even if it could as you could hard link wineserver then trick
it into loading a malicious library relative to $ORIGIN.

I think I will investigate this a bit more, but perhaps later. For now
this is fine for RPMs and packages etc, which install to /usr, as they
can simply "chmod +s wineserver" and have apps with solid audio.

thanks -mike
diff --git a/configure.ac b/configure.ac
index 2c92dc9..5706136 100644
--- a/configure.ac
+++ b/configure.ac
@@ -233,6 +233,7 @@ AC_CHECK_HEADERS(\
 	stdint.h \
 	strings.h \
 	sys/asoundlib.h \
+	sys/capability.h \
 	sys/cdio.h \
 	sys/elf32.h \
 	sys/epoll.h \
@@ -1193,6 +1194,7 @@ then
   WINE_GET_SONAME(ungif,DGifOpen)
   WINE_GET_SONAME(gif,DGifOpen)
   WINE_GET_SONAME(capi20,capi20_isinstalled)
+  WINE_GET_SONAME(cap,cap_set_proc)
 fi
 
 
diff --git a/server/main.c b/server/main.c
index 43d3cf7..d283c12 100644
--- a/server/main.c
+++ b/server/main.c
@@ -28,6 +28,13 @@
 #include <stdlib.h>
 #include <sys/time.h>
 #include <unistd.h>
+#include <dlfcn.h>
+
+#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H) && defined(SONAME_LIBCAP)
+#define USE_CAPS
+#include <sys/capability.h>
+#include <sys/prctl.h>
+#endif
 
 #include "object.h"
 #include "file.h"
@@ -108,6 +115,51 @@ static void parse_args( int argc, char *
     }
 }
 
+static void setup_security()
+{
+#ifdef USE_CAPS
+    cap_t cap;
+
+    typeof(cap_set_proc) *cap_set_proc;
+    typeof(cap_from_text) *cap_from_text;
+    typeof(cap_free) *cap_free;
+    void *libcap;
+
+    if (geteuid() != 0) return;
+    if (getuid() == 0) return;
+    
+    /* check we have libcap */
+    if ((libcap = dlopen( SONAME_LIBCAP, RTLD_LAZY )))
+    {
+        cap_set_proc  = dlsym( libcap, "cap_set_proc" );
+        cap_from_text = dlsym( libcap, "cap_from_text" );
+        cap_free      = dlsym( libcap, "cap_free" );
+
+        /* if these trigger, the user has a broken libcap */
+        assert( cap_set_proc );
+        assert( cap_from_text );
+        assert( cap_free );
+        
+        /* ok, keep root capabilities as we transition to the regular user */
+        prctl( PR_SET_KEEPCAPS, 1, 0, 0, 0 );
+    }
+    
+    /* switch user - if no libcap, we lose all root privs here */
+    setuid( getuid() );
+
+    if (libcap)
+    {
+        /* drop all privs except CAP_SYS_NICE, needed for SetThreadPriority */
+        if (cap_set_proc((cap = cap_from_text( "CAP_SYS_NICE+pe" ))) < 0)
+        {
+            perror( "wineserver: cap_set_proc: failed to drop privs, aborting" );
+            exit( 1 );
+        }
+        cap_free(cap);
+    }
+#endif    
+}
+
 static void sigterm_handler( int signum )
 {
     exit(1);  /* make sure atexit functions get called */
@@ -115,6 +167,8 @@ static void sigterm_handler( int signum 
 
 int main( int argc, char *argv[] )
 {
+    setup_security();
+    
     parse_args( argc, argv );
 
     /* setup temporary handlers before the real signal initialization is done */
diff --git a/server/thread.c b/server/thread.c
index e5ef779..ef57972 100644
--- a/server/thread.c
+++ b/server/thread.c
@@ -32,6 +32,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include <time.h>
+#ifdef HAVE_SCHED_H
+#include <sched.h>
+#endif
 #ifdef HAVE_POLL_H
 #include <poll.h>
 #endif
@@ -313,12 +316,59 @@ struct thread *get_thread_from_pid( int 
     return NULL;
 }
 
+static void set_thread_priority( int unix_tid, int ntprio )
+{
+#ifdef HAVE_SCHED_H    
+    struct sched_param param;
+    int result, scheduler;
+
+    assert( unix_tid != -1 );
+
+    if (ntprio == THREAD_PRIORITY_TIME_CRITICAL)
+    {
+        param.sched_priority = 1;
+        scheduler = SCHED_FIFO;
+    }
+    else
+    {
+        param.sched_priority = 0;
+        scheduler = SCHED_OTHER;
+    }
+    
+    result = sched_setscheduler( unix_tid, scheduler, &param );
+
+    if (result == 0) return;
+    
+    if (result == -EPERM)
+    {
+        static BOOL warned = FALSE;
+                
+        if (!warned)
+        {
+            fprintf( stderr, "\nwineserver: Failed to promote the priority of a time critical thread.\n" );
+            fprintf( stderr, "wineserver: Audio may destabilise. Try making wineserver suid root.\n" );
+            warned = TRUE;
+        }
+
+        return;
+    }
+
+    perror( "wineserver: sched_setscheduler" );
+#endif    
+}
+
 /* set all information about a thread */
 static void set_thread_info( struct thread *thread,
                              const struct set_thread_info_request *req )
 {
     if (req->mask & SET_THREAD_INFO_PRIORITY)
+    {
+        if ((thread->priority != req->priority) && (thread->unix_tid != -1))
+            set_thread_priority( thread->unix_tid, req->priority );
+        
         thread->priority = req->priority;
+    }
+
     if (req->mask & SET_THREAD_INFO_AFFINITY)
     {
         if (req->affinity != 1) set_error( STATUS_INVALID_PARAMETER );
@@ -872,6 +922,10 @@ DECL_HANDLER(init_thread)
     }
     debug_level = max( debug_level, req->debug_level );
 
+    /* we may have raced with a SetThreadPriority call */
+    if (current->priority != THREAD_PRIORITY_NORMAL)
+        set_thread_priority( current->unix_tid, current->priority );
+    
     reply->pid     = get_process_id( process );
     reply->tid     = get_thread_id( current );
     reply->version = SERVER_PROTOCOL_VERSION;


Reply via email to