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;