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, ¶m ); + + 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;