[PATCH v6 0/5] MR9843: wtsapi32: Fix session id and desktop handling when using CreateProcessAsUserW with tokens from WTSQueryUserToken
This Merge request resolves [Wine-Bug #58588](https://bugs.winehq.org/show_bug.cgi?id=58588). The underlying problem was that services started on their own, non-interactive winstation, but within the current user's session. This is trivially not the case for system services on Windows: they run, by design, in other user contexts (e.g. NTAUTHORITY\SYSTEM). Whenever a service wants to start a process within another user's graphical session, it needs to set the session id to the correct value. This may be done using the WTS API. This MR implements this functionality in a (hopefully) wine-compatible way: 1. Patch wineserver so tokens can be transferred to other sessions 2. Implement NtSetInformationToken for TokenSessionId 3. Change the session of non-interactive services to session 0 (from the default session 1) in addition to setting their desktop to `__wineservice_winstation\Default` 4. Let CreateProcessAsUserW reset the desktop to the default value `__winsta0\Default` if the session of the to-be-created process is not a service session. This avoids implementing all user and session handling in-depth while still allowing the underlying functionality to work. A real-world user of this mechanism is the Dassault 3DEXPERIENCE launcher, which runs as a system service and wants to open interactive child processes within a user's scope. -- v6: kernelbase: Let CreateProcessInternalW overwrite the desktop if needed services: start services with session id 0 wtsapi32: Make WTSQueryUserToken return a token with correct session id ntdll: Implement NtSetInformationToken for TokenSessionId https://gitlab.winehq.org/wine/wine/-/merge_requests/9843
From: Katharina Bogad <katharina@hacked.xyz> Add a server protocol message to set session ids for process tokens. --- server/protocol.def | 5 +++++ server/token.c | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/server/protocol.def b/server/protocol.def index 0ac64297026..9f210a8504d 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3550,6 +3550,11 @@ enum caret_state VARARG(acl,acl); /* default dacl to set */ @END +@REQ(set_token_session_id) + obj_handle_t handle; /* handle to the token */ + unsigned int session_id; /* session_id the token should become */ +@END + @REQ(set_security_object) obj_handle_t handle; /* handle to the object */ unsigned int security_info; /* which parts of security descriptor to set */ diff --git a/server/token.c b/server/token.c index 5ce7298211e..b8a214fd21e 100644 --- a/server/token.c +++ b/server/token.c @@ -1602,6 +1602,20 @@ DECL_HANDLER(set_token_default_dacl) } } +DECL_HANDLER(set_token_session_id) +{ + struct token *token; + + if((token = (struct token *)get_handle_obj( current->process, req->handle, + TOKEN_ADJUST_DEFAULT, + &token_ops))) + { + token->session_id = req->session_id; + + release_object( token ); + } +} + DECL_HANDLER(create_linked_token) { struct token *token, *linked; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9843
From: Katharina Bogad <katharina@hacked.xyz> --- dlls/ntdll/unix/security.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dlls/ntdll/unix/security.c b/dlls/ntdll/unix/security.c index 949af82f783..aff8763ee75 100644 --- a/dlls/ntdll/unix/security.c +++ b/dlls/ntdll/unix/security.c @@ -702,7 +702,7 @@ NTSTATUS WINAPI NtSetInformationToken( HANDLE token, TOKEN_INFORMATION_CLASS cla break; case TokenSessionId: - if (length < sizeof(DWORD)) + if (length < sizeof(ULONG)) { ret = STATUS_INFO_LENGTH_MISMATCH; break; @@ -712,7 +712,13 @@ NTSTATUS WINAPI NtSetInformationToken( HANDLE token, TOKEN_INFORMATION_CLASS cla ret = STATUS_ACCESS_VIOLATION; break; } - FIXME("TokenSessionId stub!\n"); + SERVER_START_REQ( set_token_session_id ) + { + req->handle = wine_server_obj_handle( token ); + req->session_id = *((ULONG *)info); + ret = wine_server_call(req); + } + SERVER_END_REQ; ret = STATUS_SUCCESS; break; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9843
From: Katharina Bogad <katharina@hacked.xyz> On Windows, WTSQueryUserToken called with a session_id returns a token that is in that user's current session and winstation. Since WINE lacks handling for multiple users, make WTSQueryUserToken return the duplicated token with the requested session_id set. --- dlls/wtsapi32/wtsapi32.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dlls/wtsapi32/wtsapi32.c b/dlls/wtsapi32/wtsapi32.c index 5a1a87f416e..0a2e25bb9cd 100644 --- a/dlls/wtsapi32/wtsapi32.c +++ b/dlls/wtsapi32/wtsapi32.c @@ -653,9 +653,13 @@ BOOL WINAPI WTSQueryUserToken(ULONG session_id, PHANDLE token) return FALSE; } - return DuplicateHandle(GetCurrentProcess(), GetCurrentProcessToken(), + if(!DuplicateHandle(GetCurrentProcess(), GetCurrentProcessToken(), GetCurrentProcess(), token, - 0, FALSE, DUPLICATE_SAME_ACCESS); + 0, FALSE, DUPLICATE_SAME_ACCESS)) + return FALSE; + + + return SetTokenInformation(*token, TokenSessionId, &session_id, sizeof(ULONG)); } /************************************************************ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9843
From: Katharina Bogad <katharina@hacked.xyz> Services run in a non-interactive session by default; this session is also not enumeratable in WTSEnumerateSessions. Previously, the first part was handled by overwriting the winstation to __wineservice_winstation\Default while leaving the session id to the default 1. Change the default service session to session id 0 when starting a service and fixup WTSEnumerateSessions to never return the service session id. --- dlls/wtsapi32/wtsapi32.c | 5 +++++ programs/services/services.c | 21 +++++++++++++++++---- server/user.h | 1 + server/winstation.c | 6 ++++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/dlls/wtsapi32/wtsapi32.c b/dlls/wtsapi32/wtsapi32.c index 0a2e25bb9cd..3db47731945 100644 --- a/dlls/wtsapi32/wtsapi32.c +++ b/dlls/wtsapi32/wtsapi32.c @@ -364,6 +364,11 @@ BOOL WINAPI WTSEnumerateSessionsW(HANDLE server, DWORD reserved, DWORD version, WTSFreeMemory(*session_info); return FALSE; } + if ((*session_info)->SessionId == 0) + { + FIXME("replacing service session with default\n"); + (*session_info)->SessionId = 1 /* default_session_id, server/process.h */; + } *count = 1; (*session_info)->State = WTSActive; (*session_info)->pWinStationName = (WCHAR *)((char *)*session_info + sizeof(**session_info)); diff --git a/programs/services/services.c b/programs/services/services.c index dff3cd60040..933e8bb2689 100644 --- a/programs/services/services.c +++ b/programs/services/services.c @@ -918,7 +918,7 @@ static DWORD service_start_process(struct service_entry *service_entry, struct p BOOL is_wow64 = FALSE; HANDLE token; WCHAR *path; - DWORD err; + DWORD err, service_session_id = 0; BOOL r; service_lock(service_entry); @@ -1028,7 +1028,10 @@ found: si.lpDesktop = (WCHAR *)L"__wineservice_winstation\\Default"; } - if (!environment && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE, &token)) + r = DuplicateHandle(GetCurrentProcess(), GetCurrentProcessToken(), + GetCurrentProcess(), &token, 0, FALSE, DUPLICATE_SAME_ACCESS); + TRACE("DuplicateHandle %u %lu\n", r, GetLastError()); + if (!environment && r ) { WCHAR val[16]; CreateEnvironmentBlock(&environment, token, FALSE); @@ -1040,7 +1043,6 @@ found: RtlInitUnicodeString( &value, val ); RtlSetEnvironmentVariable( (WCHAR **)&environment, &name, &value ); } - CloseHandle(token); } service_entry->status.dwCurrentState = SERVICE_START_PENDING; @@ -1053,8 +1055,19 @@ found: process->use_count++; service_unlock(service_entry); - r = CreateProcessW(NULL, path, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, environment, NULL, &si, &pi); + r = SetTokenInformation(token, TokenSessionId, &service_session_id, sizeof(service_session_id)); + + if(!r) + { + err = GetLastError(); + process_terminate(process); + release_process(process); + return err; + } + + r = CreateProcessAsUserW(token, NULL, path, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, environment, NULL, &si, &pi); free(path); + CloseHandle(token); if (!r) { err = GetLastError(); diff --git a/server/user.h b/server/user.h index 9f6c3dcaf0f..d2063f19aa9 100644 --- a/server/user.h +++ b/server/user.h @@ -49,6 +49,7 @@ struct winstation unsigned int monitor_count; /* number of monitors */ struct monitor_info *monitors; /* window station monitors */ unsigned __int64 monitor_serial; /* winstation monitor update counter */ + unsigned int session_id; /* session id this winstation belongs to */ }; struct key_repeat diff --git a/server/winstation.c b/server/winstation.c index a36253e20db..f6793f3ebba 100644 --- a/server/winstation.c +++ b/server/winstation.c @@ -136,7 +136,7 @@ static const struct object_ops desktop_ops = /* create a winstation object */ static struct winstation *create_winstation( struct object *root, const struct unicode_str *name, - unsigned int attr, unsigned int flags ) + unsigned int attr, unsigned int flags, unsigned int session_id ) { struct winstation *winstation; @@ -152,6 +152,7 @@ static struct winstation *create_winstation( struct object *root, const struct u winstation->monitors = NULL; winstation->monitor_count = 0; winstation->monitor_serial = 1; + winstation->session_id = session_id; list_add_tail( &winstation_list, &winstation->entry ); list_init( &winstation->desktops ); if (!(winstation->desktop_names = create_namespace( 7 ))) @@ -612,7 +613,7 @@ DECL_HANDLER(create_winstation) reply->handle = 0; if (req->rootdir && !(root = get_directory_obj( current->process, req->rootdir ))) return; - if ((winstation = create_winstation( root, &name, req->attributes, req->flags ))) + if ((winstation = create_winstation( root, &name, req->attributes, req->flags, current->process->session_id ))) { reply->handle = alloc_handle( current->process, winstation, req->access, req->attributes ); release_object( winstation ); @@ -987,6 +988,7 @@ DECL_HANDLER(enum_winstation) unsigned int access = WINSTA_ENUMERATE; if (!(name = winstation->obj.name)) continue; if (!check_object_access( NULL, &winstation->obj, &access )) continue; + if (current->process->session_id != winstation->session_id) continue; reply->count++; reply->total += name->len + sizeof(WCHAR); if (reply->total <= size) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9843
From: Katharina Bogad <katharina@hacked.xyz> Calling CreateProcessAsUserW with a token that has session_id != 0 set should result in the program being started on the token's assoiciated desktop. Currently, WINE doesn't handle multiple users and multiple winstations, so for the moment, if the associated token has a session_id != 0, overwrite the desktop to the default value. This allows serivces started with session_id 0 running non-interactively to get a session_id from wtsapi32 belonging to an interactive session and use CreateProcessAsUserW to open child processes in an interactive environment. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=58588 --- dlls/kernelbase/process.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/dlls/kernelbase/process.c b/dlls/kernelbase/process.c index 3656e40280d..d7cd32a4944 100644 --- a/dlls/kernelbase/process.c +++ b/dlls/kernelbase/process.c @@ -519,6 +519,11 @@ BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessInternalW( HANDLE token, const WCHAR ULONG nt_flags = 0; USHORT machine = 0; NTSTATUS status; + DWORD dwSessionId = 0; + DWORD dwReturnLength; + STARTUPINFOW local_startup_info; + BOOL success; + /* Process the AppName and/or CmdLine to get module name and path */ @@ -541,6 +546,26 @@ BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessInternalW( HANDLE token, const WCHAR app_name = name; } + /* Fixup startupinfo->lpDesktop if needed */ + + memcpy(&local_startup_info, startup_info, sizeof(STARTUPINFOW)); + if(token != NULL) + { + success = GetTokenInformation(token, TokenSessionId, &dwSessionId, sizeof(dwSessionId), &dwReturnLength); + + TRACE("success = %u, dwSessionId = %lu\n", success, dwSessionId); + + + if (success && dwSessionId != 0) + { + /* the created process should be attached to an interactive session. + * So we override the winstation for this process. */ + FIXME( "Child process will be started with default desktop\n"); + + local_startup_info.lpDesktop = (WCHAR*)L"winsta0\\default"; + } + } + /* Warn if unsupported features are used */ if (flags & (IDLE_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | REALTIME_PRIORITY_CLASS | @@ -560,7 +585,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH CreateProcessInternalW( HANDLE token, const WCHAR info->hThread = info->hProcess = 0; info->dwProcessId = info->dwThreadId = 0; - if (!(params = create_process_params( app_name, tidy_cmdline, cur_dir, env, flags, startup_info ))) + if (!(params = create_process_params( app_name, tidy_cmdline, cur_dir, env, flags, &local_startup_info ))) { status = STATUS_NO_MEMORY; goto done; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9843
participants (2)
-
Katharina Bogad -
Katharina Bogad (@hackathi)