try following locations for wineserver directory: - `${XDG_RUNTIME_DIR}/wine` - `/run/user/${uid}/wine` - `${TMPDIR}/wine` - only if `${TMPDIR}` is owned by user - `${TMPDIR}/.wine-${uid}` - `/tmp/.wine-${uid}`
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=39013
Signed-off-by: Konstantin Demin rockdrilla@gmail.com
-- v12: ntdll: Try isolate wineserver directory. server: check directory access permissions.
From: Konstantin Demin rockdrilla@gmail.com
wineserver directory must be fully accessible by user
Signed-off-by: Konstantin Demin rockdrilla@gmail.com --- server/request.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/server/request.c b/server/request.c index 432a5918892..273c9d84b5f 100644 --- a/server/request.c +++ b/server/request.c @@ -591,6 +591,7 @@ static void create_dir( const char *name, struct stat *st ) if (!S_ISDIR(st->st_mode)) fatal_error( "%s is not a directory\n", name ); if (st->st_uid != getuid()) fatal_error( "%s is not owned by you\n", name ); if (st->st_mode & 077) fatal_error( "%s must not be accessible by other users\n", name ); + if ((st->st_mode & 0700) != 0700) fatal_error( "%s must be writeable by you\n", name ); }
/* create the server directory and chdir to it */
From: Konstantin Demin rockdrilla@gmail.com
try following locations for wineserver directory: - ${XDG_RUNTIME_DIR}/wine - /run/user/${uid}/wine - ${TMPDIR}/wine - only if ${TMPDIR} is owned by user - ${TMPDIR}/.wine-${uid} - /tmp/.wine-${uid} - default behavior
Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=14838 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=39013
Signed-off-by: Konstantin Demin rockdrilla@gmail.com --- dlls/ntdll/unix/server.c | 22 +++- include/Makefile.in | 1 + include/wine/server_workdir.h | 191 ++++++++++++++++++++++++++++++++++ server/request.c | 13 ++- 4 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 include/wine/server_workdir.h
diff --git a/dlls/ntdll/unix/server.c b/dlls/ntdll/unix/server.c index 258a959de72..d8a2887ea04 100644 --- a/dlls/ntdll/unix/server.c +++ b/dlls/ntdll/unix/server.c @@ -81,6 +81,10 @@ #include "unix_private.h" #include "ddk/wdm.h"
+#ifndef __ANDROID__ +#include "wine/server_workdir.h" +#endif + WINE_DEFAULT_DEBUG_CHANNEL(server); WINE_DECLARE_DEBUG_CHANNEL(syscall);
@@ -1303,13 +1307,25 @@ int server_pipe( int fd[2] ) */ static const char *init_server_dir( dev_t dev, ino_t ino ) { - char *dir = NULL; + char *base_dir = NULL, *dir = NULL;
#ifdef __ANDROID__ /* there's no /tmp dir on Android */ - asprintf( &dir, "%s/.wineserver/server-%llx-%llx", config_dir, (unsigned long long)dev, (unsigned long long)ino ); + if (asprintf( &base_dir, "%s/.wineserver", config_dir ) < 0) + fatal_error( "out of memory\n" ); #else - asprintf( &dir, "/tmp/.wine-%u/server-%llx-%llx", getuid(), (unsigned long long)dev, (unsigned long long)ino ); + if (!(base_dir = wineserver_workdir())) + { + if (errno == ENOMEM) fatal_error( "out of memory\n" ); + fatal_error( "error while building wineserver directory\n" ); + } #endif + + if (asprintf( &dir, "%s/server-%llx-%llx", base_dir, (unsigned long long) dev, (unsigned long long) ino ) < 0) + fatal_error( "out of memory\n" ); + + /* not needed anymore */ + free( base_dir ); + return dir; }
diff --git a/include/Makefile.in b/include/Makefile.in index 6b33733a6f2..08e29418552 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -984,6 +984,7 @@ SOURCES = \ wine/schrpc.idl \ wine/server.h \ wine/server_protocol.h \ + wine/server_workdir.h \ wine/strmbase.h \ wine/svcctl.idl \ wine/test.h \ diff --git a/include/wine/server_workdir.h b/include/wine/server_workdir.h new file mode 100644 index 00000000000..8927cc8c7ed --- /dev/null +++ b/include/wine/server_workdir.h @@ -0,0 +1,191 @@ +/* + * Wine server directory handling + * + * Copyright 2022 Konstantin Demin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef __WINE_WINE_SERVER_WORKDIR_H +#define __WINE_WINE_SERVER_WORKDIR_H 1 + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <dirent.h> +#include <pthread.h> +#include <sys/stat.h> +#include <sys/types.h> + + +static int wineserver_workdir_test_path( struct stat *st, int per_user ) +{ + if (!st) return 1; + + /* NB: return codes must be distinct and aligned with wineserver_workdir_try_path() */ + + if (!S_ISDIR( st->st_mode )) + { + errno = ENOTDIR; + return 3; + } + + if (!per_user) return 0; + + if (st->st_uid != getuid()) return 4; + if (st->st_mode & 077) return 5; + if ((st->st_mode & 0700) != 0700) return 6; + + return 0; +} + +static int wineserver_workdir_try_path( const char *path, struct stat *st, int per_user ) +{ + if ((!path) || (!st)) return 1; + + (void) memset( st, 0, sizeof(struct stat) ); + errno = 0; + if (lstat( path, st ) == -1) return 2; + + return wineserver_workdir_test_path( st, per_user ); +} + +/* + * try following locations for wineserver directory: + * - ${XDG_RUNTIME_DIR}/wine + * - /run/user/${uid}/wine + * - ${TMPDIR}/wine - only if ${TMPDIR} is per-user + * - ${TMPDIR}/.wine-${uid} + * - /tmp/.wine-${uid} - default behavior + */ + +static inline char *wineserver_workdir_impl(void) +{ + const char *rootdir = NULL; + char *workdir = NULL; + struct stat st1; + + /* try "${XDG_RUNTIME_DIR}/wine" */ + do { + rootdir = getenv( "XDG_RUNTIME_DIR" ); + if (!rootdir) break; + if ((!rootdir[0]) || (rootdir[0] != '/')) break; + + if (wineserver_workdir_try_path( rootdir, &st1, 1 /* per-user */ )) break; + + errno = 0; + if (asprintf( &workdir, "%s/wine", rootdir ) < 0) + { + if (errno == 0) errno = ENOMEM; + return NULL; + } + + errno = 0; + return workdir; + } while (0); + + /* try "/run/user/${uid}/wine" */ + do { + const size_t uid_len = 11; /* maximum uid length */ + const size_t full_len = sizeof( "/run/user/" ) + uid_len + sizeof( "/wine" ) + 1 /* NUL */; + + errno = 0; + workdir = (char *) malloc( full_len ); + if (!workdir) + { + if (errno == 0) errno = ENOMEM; + return NULL; + } + (void) memset( workdir, 0, full_len ); + + /* NB: safe enough - space is already allocated */ + (void) sprintf( workdir, "/run/user/%u", getuid() ); + if (wineserver_workdir_try_path( workdir, &st1, 1 /* per-user */ )) + { + free( workdir ); workdir = NULL; + break; + } + + /* NB: safe enough - space is already allocated */ + (void) strcat( workdir, "/wine" ); + + errno = 0; + return workdir; + } while (0); + + /* try somewhere in ${TMPDIR}/ */ + do { + rootdir = getenv( "TMPDIR" ); + if (!rootdir) break; + if ((!rootdir[0]) || (rootdir[0] != '/')) break; + + /* verify directory existence */ + if (wineserver_workdir_try_path( rootdir, &st1, 0 /* shared */ )) break; + + if (wineserver_workdir_test_path( &st1, 1 /* per-user */ )) + { + /* ${TMPDIR} is NOT per-user - use "${TMPDIR}/.wine-${uid}" */ + errno = 0; + if (asprintf( &workdir, "%s/.wine-%u", rootdir, getuid() ) < 0) + { + if (errno == 0) errno = ENOMEM; + return NULL; + } + } + else + { + /* ${TMPDIR} is per-user - use "${TMPDIR}/wine" */ + errno = 0; + if (asprintf( &workdir, "%s/wine", rootdir ) < 0) + { + if (errno == 0) errno = ENOMEM; + return NULL; + } + } + + errno = 0; + return workdir; + } while (0); + + /* LAST RESORT: use "/tmp/.wine-${uid}" */ + errno = 0; + if (asprintf( &workdir, "/tmp/.wine-%u", getuid() ) < 0) + { + if (errno == 0) errno = ENOMEM; + return NULL; + } + + errno = 0; + return workdir; +} + +static char *__wineserver_workdir = NULL; +static __attribute__((noinline)) void wineserver_workdir_once(void) +{ + __wineserver_workdir = wineserver_workdir_impl(); +} + +static __attribute__((noinline)) char *wineserver_workdir(void) +{ + static pthread_once_t _once = PTHREAD_ONCE_INIT; + + (void) pthread_once( &_once, wineserver_workdir_once ); + return __wineserver_workdir; +} + +#endif /* __WINE_WINE_SERVER_WORKDIR_H */ diff --git a/server/request.c b/server/request.c index 273c9d84b5f..a77ad5f0184 100644 --- a/server/request.c +++ b/server/request.c @@ -62,6 +62,10 @@ #include "handle.h" #include "request_handlers.h"
+#ifndef __ANDROID__ +#include "wine/server_workdir.h" +#endif + /* Some versions of glibc don't define this */ #ifndef SCM_RIGHTS #define SCM_RIGHTS 1 @@ -642,11 +646,14 @@ static char *create_server_dir( int force ) /* create the base directory if needed */
#ifdef __ANDROID__ /* there's no /tmp dir on Android */ - if (asprintf( &base_dir, "%s/.wineserver", config_dir ) == -1) + if (asprintf( &base_dir, "%s/.wineserver", config_dir ) < 0) fatal_error( "out of memory\n" ); #else - if (asprintf( &base_dir, "/tmp/.wine-%u", getuid() ) == -1) - fatal_error( "out of memory\n" ); + if (!(base_dir = wineserver_workdir())) + { + if (errno == ENOMEM) fatal_error( "out of memory\n" ); + fatal_error( "error while building wineserver directory\n" ); + } #endif create_dir( base_dir, &st2 );
On Thu Oct 30 10:37:31 2025 +0000, Konstantin Demin wrote:
Incidentally I'm refactoring it right now to simplify it as possible.
I think I did my best. IMO not relying on environment variables is kind of way to hardcode things across entire source code and self-harm - for both developers and end users.