-- v2: ntdll: Implement NtCreateToken().
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/ntdll/ntdll.spec | 4 +- dlls/ntdll/unix/loader.c | 1 + dlls/ntdll/unix/security.c | 112 +++++++++++++++++++++++++++++++++++++ dlls/wow64/security.c | 36 ++++++++++++ dlls/wow64/struct32.h | 5 ++ dlls/wow64/syscall.h | 1 + dlls/wow64/wow64_private.h | 25 +++++++++ server/protocol.def | 19 +++++++ server/token.c | 101 +++++++++++++++++++++++++++++++++ 9 files changed, 302 insertions(+), 2 deletions(-)
diff --git a/dlls/ntdll/ntdll.spec b/dlls/ntdll/ntdll.spec index 011f0648522..37fc5be61d3 100644 --- a/dlls/ntdll/ntdll.spec +++ b/dlls/ntdll/ntdll.spec @@ -192,7 +192,7 @@ @ stdcall -syscall NtCreateThread(ptr long ptr long ptr ptr ptr long) @ stdcall -syscall NtCreateThreadEx(ptr long ptr long ptr ptr long long long long ptr) @ stdcall -syscall NtCreateTimer(ptr long ptr long) -# @ stub NtCreateToken +@ stdcall -syscall NtCreateToken(ptr long ptr long ptr ptr ptr ptr ptr ptr ptr ptr ptr) @ stdcall -syscall NtCreateTransaction(ptr long ptr ptr long long long long ptr ptr) @ stdcall -syscall NtCreateUserProcess(ptr ptr long long ptr ptr long long ptr ptr ptr) # @ stub NtCreateWaitablePort @@ -1235,7 +1235,7 @@ @ stdcall -private -syscall ZwCreateThread(ptr long ptr long ptr ptr ptr long) NtCreateThread @ stdcall -private -syscall ZwCreateThreadEx(ptr long ptr long ptr ptr long long long long ptr) NtCreateThreadEx @ stdcall -private -syscall ZwCreateTimer(ptr long ptr long) NtCreateTimer -# @ stub ZwCreateToken +@ stdcall -private -syscall ZwCreateToken(ptr long ptr long ptr ptr ptr ptr ptr ptr ptr ptr ptr) NtCreateToken @ stdcall -private -syscall ZwCreateUserProcess(ptr ptr long long ptr ptr long long ptr ptr ptr) NtCreateUserProcess # @ stub ZwCreateWaitablePort @ stdcall -private -syscall ZwDebugActiveProcess(long long) NtDebugActiveProcess diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c index 28f0c4bf245..5824712b110 100644 --- a/dlls/ntdll/unix/loader.c +++ b/dlls/ntdll/unix/loader.c @@ -166,6 +166,7 @@ static void * const syscalls[] = NtCreateThread, NtCreateThreadEx, NtCreateTimer, + NtCreateToken, NtCreateTransaction, NtCreateUserProcess, NtDebugActiveProcess, diff --git a/dlls/ntdll/unix/security.c b/dlls/ntdll/unix/security.c index 8351f8b1993..25d36d15dd5 100644 --- a/dlls/ntdll/unix/security.c +++ b/dlls/ntdll/unix/security.c @@ -36,6 +36,118 @@
WINE_DEFAULT_DEBUG_CHANNEL(ntdll);
+/*********************************************************************** + * NtCreateToken (NTDLL.@) + */ +NTSTATUS WINAPI NtCreateToken( HANDLE *handle, ACCESS_MASK access, OBJECT_ATTRIBUTES *attr, + TOKEN_TYPE type, LUID *token_id, LARGE_INTEGER *expire, TOKEN_USER *user, + TOKEN_GROUPS *groups, TOKEN_PRIVILEGES *privs, TOKEN_OWNER *owner, + TOKEN_PRIMARY_GROUP *group, TOKEN_DEFAULT_DACL *dacl, TOKEN_SOURCE *source) +{ + SECURITY_IMPERSONATION_LEVEL level = SecurityAnonymous; + unsigned int status, group_count, i, *attrs; + data_size_t objattr_size, groups_size, size; + struct object_attributes *objattr; + void *groups_info; + BYTE *tmp; + SID *sid; + + TRACE( "(%p,0x%08x,%p,%d,%p,%p,%p,%p,%p,%p,%p,%p,%p)\n", handle, (int)access, attr, + type, token_id, expire, user, groups, privs, owner, group, dacl, source ); + + if (!handle || !attr || !expire || !user || !groups || !privs) + return STATUS_INVALID_PARAMETER; + + *handle = 0; + if ((status = alloc_object_attributes( attr, &objattr, &objattr_size ))) return status; + + if (attr && attr->SecurityQualityOfService) + { + SECURITY_QUALITY_OF_SERVICE *qos = attr->SecurityQualityOfService; + TRACE( "ObjectAttributes->SecurityQualityOfService = {%d, %d, %d, %s}\n", + (int)qos->Length, qos->ImpersonationLevel, qos->ContextTrackingMode, + qos->EffectiveOnly ? "TRUE" : "FALSE"); + level = qos->ImpersonationLevel; + } + + group_count = groups->GroupCount; + groups_size = group_count * sizeof( attrs[0] ); + + for (i = 0; i < group_count; i++) + { + sid = groups->Groups[i].Sid; + groups_size += offsetof( SID, SubAuthority[sid->SubAuthorityCount] ); + } + + if (group) + { + sid = group->PrimaryGroup; + groups_size += sizeof( attrs[0] ) + offsetof( SID, SubAuthority[sid->SubAuthorityCount] ); + group_count++; + } + + groups_info = malloc( groups_size ); + if (!groups_info) + { + free( objattr ); + return STATUS_NO_MEMORY; + } + + attrs = (unsigned int *)groups_info; + tmp = (BYTE *)&attrs[group_count]; + for (i = 0; i < groups->GroupCount; i++) + { + sid = groups->Groups[i].Sid; + attrs[i] = groups->Groups[i].Attributes; + size = offsetof( SID, SubAuthority[sid->SubAuthorityCount] ); + memcpy( tmp, sid, size ); + tmp += size; + } + + if (group) + { + sid = group->PrimaryGroup; + attrs[group_count - 1] = SE_GROUP_ENABLED|SE_GROUP_ENABLED_BY_DEFAULT|SE_GROUP_MANDATORY|SE_GROUP_OWNER; + size = offsetof( SID, SubAuthority[sid->SubAuthorityCount] ); + memcpy( tmp, sid, size ); + } + + SERVER_START_REQ( create_token ) + { + SID *sid; + + req->token_id.low_part = token_id->LowPart; + req->token_id.high_part = token_id->HighPart; + req->access = access; + req->primary = (type == TokenPrimary); + req->impersonation_level = level; + req->expire = expire->QuadPart; + + wine_server_add_data( req, objattr, objattr_size ); + + sid = user->User.Sid; + wine_server_add_data( req, sid, offsetof( SID, SubAuthority[sid->SubAuthorityCount] ) ); + + req->group_count = group_count; + wine_server_add_data( req, groups_info, groups_size ); + + req->priv_count = privs->PrivilegeCount; + wine_server_add_data( req, privs->Privileges, privs->PrivilegeCount * sizeof(privs->Privileges[0]) ); + + if (dacl && dacl->DefaultDacl) + wine_server_add_data( req, dacl->DefaultDacl, dacl->DefaultDacl->AclSize ); + + status = wine_server_call( req ); + if (!status) *handle = wine_server_ptr_handle( reply->token ); + } + SERVER_END_REQ; + + free( groups_info ); + free( objattr ); + + return status; +} +
/*********************************************************************** * NtOpenProcessToken (NTDLL.@) diff --git a/dlls/wow64/security.c b/dlls/wow64/security.c index 82bbb0d2b40..26d413baa13 100644 --- a/dlls/wow64/security.c +++ b/dlls/wow64/security.c @@ -154,6 +154,42 @@ NTSTATUS WINAPI wow64_NtCreateLowBoxToken( UINT *args ) }
+/********************************************************************** + * wow64_NtCreateToken + */ +NTSTATUS WINAPI wow64_NtCreateToken( UINT *args ) +{ + ULONG *handle_ptr = get_ptr( &args ); + ACCESS_MASK access = get_ulong( &args ); + OBJECT_ATTRIBUTES32 *attr32 = get_ptr( &args ); + TOKEN_TYPE type = get_ulong( &args ); + LUID *luid = get_ptr( &args ); + LARGE_INTEGER *expire = get_ptr( &args ); + TOKEN_USER32 *user32 = get_ptr( &args ); + TOKEN_GROUPS32 *groups32 = get_ptr( &args ); + TOKEN_PRIVILEGES *privs = get_ptr( &args ); + TOKEN_OWNER32 *owner32 = get_ptr( &args ); + TOKEN_PRIMARY_GROUP32 *group32 = get_ptr( &args ); + TOKEN_DEFAULT_DACL32 *dacl32 = get_ptr( &args ); + TOKEN_SOURCE *source = get_ptr( &args ); + + struct object_attr64 attr; + TOKEN_USER user; + TOKEN_OWNER owner; + TOKEN_PRIMARY_GROUP group; + TOKEN_DEFAULT_DACL dacl; + HANDLE handle = 0; + NTSTATUS status; + + status = NtCreateToken( &handle, access, objattr_32to64( &attr, attr32 ), type, luid, expire, + token_user_32to64( &user, user32 ), token_groups_32to64( groups32 ), privs, + token_owner_32to64( &owner, owner32 ), token_primary_group_32to64( &group, group32 ), + token_default_dacl_32to64( &dacl, dacl32 ), source ); + put_handle( handle_ptr, handle ); + return status; +} + + /********************************************************************** * wow64_NtDuplicateToken */ diff --git a/dlls/wow64/struct32.h b/dlls/wow64/struct32.h index 7dc12b75271..2e51e89fba0 100644 --- a/dlls/wow64/struct32.h +++ b/dlls/wow64/struct32.h @@ -372,6 +372,11 @@ typedef struct ULONG Owner; } TOKEN_OWNER32;
+typedef struct +{ + ULONG PrimaryGroup; +} TOKEN_PRIMARY_GROUP32; + typedef struct { SID_AND_ATTRIBUTES32 User; diff --git a/dlls/wow64/syscall.h b/dlls/wow64/syscall.h index 59586d3fbfa..fce71395439 100644 --- a/dlls/wow64/syscall.h +++ b/dlls/wow64/syscall.h @@ -70,6 +70,7 @@ SYSCALL_ENTRY( NtCreateThread ) \ SYSCALL_ENTRY( NtCreateThreadEx ) \ SYSCALL_ENTRY( NtCreateTimer ) \ + SYSCALL_ENTRY( NtCreateToken ) \ SYSCALL_ENTRY( NtCreateTransaction ) \ SYSCALL_ENTRY( NtCreateUserProcess ) \ SYSCALL_ENTRY( NtDebugActiveProcess ) \ diff --git a/dlls/wow64/wow64_private.h b/dlls/wow64/wow64_private.h index 73a100cba32..9c9f03042f7 100644 --- a/dlls/wow64/wow64_private.h +++ b/dlls/wow64/wow64_private.h @@ -187,6 +187,31 @@ static inline OBJECT_ATTRIBUTES *objattr_32to64_redirect( struct object_attr64 * return attr; }
+static inline TOKEN_USER *token_user_32to64( TOKEN_USER *out, const TOKEN_USER32 *in ) +{ + out->User.Sid = ULongToPtr( in->User.Sid ); + out->User.Attributes = in->User.Attributes; + return out; +} + +static inline TOKEN_OWNER *token_owner_32to64( TOKEN_OWNER *out, const TOKEN_OWNER32 *in ) +{ + out->Owner = ULongToPtr( in->Owner ); + return out; +} + +static inline TOKEN_PRIMARY_GROUP *token_primary_group_32to64( TOKEN_PRIMARY_GROUP *out, const TOKEN_PRIMARY_GROUP32 *in ) +{ + out->PrimaryGroup = ULongToPtr( in->PrimaryGroup ); + return out; +} + +static inline TOKEN_DEFAULT_DACL *token_default_dacl_32to64( TOKEN_DEFAULT_DACL *out, const TOKEN_DEFAULT_DACL32 *in ) +{ + out->DefaultDacl = ULongToPtr( in->DefaultDacl ); + return out; +} + static inline void put_handle( ULONG *handle32, HANDLE handle ) { *handle32 = HandleToULong( handle ); diff --git a/server/protocol.def b/server/protocol.def index 919297c818c..59dceb65b8d 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -3186,6 +3186,25 @@ enum caret_state @END
+/* Create a security token */ +@REQ(create_token) + struct luid token_id; + unsigned int access; /* access rights to the new token */ + int primary; /* is the new token to be a primary one? */ + int impersonation_level; /* impersonation level of the new token */ + abstime_t expire; /* expiration time */ + int group_count; + int priv_count; + /* VARARG(objattr,object_attributes); */ + /* VARARG(user,sid); */ + /* VARARG(groups,sid); */ + /* VARARG(privs,luid_attr); */ + /* VARARG(dacl,acl); */ +@REPLY + obj_handle_t token; /* handle to the token */ +@END + + /* Open a security token */ @REQ(open_token) obj_handle_t handle; /* handle to the thread or process */ diff --git a/server/token.c b/server/token.c index 99f5e36e279..1c41c1287cc 100644 --- a/server/token.c +++ b/server/token.c @@ -1089,6 +1089,107 @@ int check_object_access(struct token *token, struct object *obj, unsigned int *a }
+/* create a security token */ +DECL_HANDLER(create_token) +{ + struct token *token; + struct object_attributes *objattr; + struct sid *user; + struct sid_attrs *groups = NULL; + struct luid_attr *privs = NULL; + struct acl *dacl = NULL; + unsigned int group_count = 0, priv_count = 0, i; + data_size_t data_size, groups_size = 0; + struct acl *default_dacl = NULL; + + objattr = (struct object_attributes *)get_req_data(); + user = (struct sid *)get_req_data_after_objattr( objattr, &data_size ); + + if (!user || !sid_valid_size( user, data_size )) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + data_size -= sid_len( user ); + + if (data_size) + { + unsigned int *attrs = (unsigned int *)((char *)user + sid_len( user )); + struct sid *sid = (struct sid *)&attrs[req->group_count]; + + group_count = req->group_count; + groups_size = group_count * sizeof( attrs[0] ); + + if (data_size < groups_size) + { + set_error( STATUS_INVALID_PARAMETER ); + return; + } + + groups = malloc( group_count * sizeof( groups[0] ) ); + if (!groups) + { + set_error( STATUS_NO_MEMORY ); + return; + } + + for (i = 0; i < group_count; i++) + { + groups[i].attrs = attrs[i]; + groups[i].sid = sid; + + if (!sid_valid_size( sid, data_size - groups_size )) + { + free( groups ); + set_error( STATUS_INVALID_PARAMETER ); + return; + } + + groups_size += sid_len( sid ); + sid = (struct sid *)((char *)sid + sid_len( sid )); + } + + data_size -= groups_size; + } + + if (data_size) + { + priv_count = req->priv_count; + + if (data_size < priv_count * sizeof( privs[0] )) + { + free( groups ); + set_error( STATUS_INVALID_PARAMETER ); + return; + } + + data_size -= priv_count * sizeof( privs[0] ); + privs = (struct luid_attr *)((char *)groups + groups_size); + } + + if (data_size) + { + dacl = (struct acl *)((char *)privs + priv_count * sizeof(privs[0])); + if (!acl_is_valid( dacl, data_size )) + { + free( groups ); + set_error( STATUS_INVALID_PARAMETER ); + return; + } + } + else + dacl = default_dacl = create_default_dacl( &domain_users_sid ); + + token = create_token( req->primary, default_session_id, user, groups, group_count, + privs, priv_count, dacl, NULL, req->impersonation_level, 0 ); + if (token) + reply->token = alloc_handle( current->process, token, req->access, objattr->attributes ); + + free( default_dacl ); + free( groups ); +} + + /* open a security token */ DECL_HANDLER(open_token) {
This could use some tests.
This could use some tests.
Unfortunately NtCreateToken() requires special priviledges and it's impossible to test from a normal user mode process under Windows.
On Tue Aug 15 19:31:55 2023 +0000, Dmitry Timoshkov wrote:
This could use some tests.
Unfortunately NtCreateToken() requires special priviledges and it's impossible to test from a normal user mode process under Windows.
Right, it requires SE_CREATE_TOKEN_PRIVILEGE, which is not assigned by default. You'd need to assign it to the Administrators group for example (using the policy editor) and reboot. Then elevate and run the test.
I guess part of it could be tested indirectly via LogonUser(). When I looked into it this picture emerged:
LogonUser() calls LsaLogonUser() which looks up the authentication provider, calls its LsaLogonUser() method and creates a token using returned token info.
Right, it requires SE_CREATE_TOKEN_PRIVILEGE, which is not assigned by default. You'd need to assign it to the Administrators group for example (using the policy editor) and reboot. Then elevate and run the test.
I guess part of it could be tested indirectly via LogonUser(). When I looked into it this picture emerged:
LogonUser() calls LsaLogonUser() which looks up the authentication provider, calls its LsaLogonUser() method and creates a token using returned token info.
It sounds like you are proposing to create a standalone test. I used https://github.com/cubiclesoft/createprocess-windows as a test.
I dug up my tests for NtCreateToken() from a backup and rebased them. We may not want them in Wine because of the manual steps required but they could still be useful for development. [createtoken.diff](/uploads/367d5d8eed8ed92d5bbce51138af92de/createtoken.diff)
I dug up my tests for NtCreateToken() from a backup and rebased them. We may not want them in Wine because of the manual steps required but they could still be useful for development. [createtoken.diff](/uploads/367d5d8eed8ed92d5bbce51138af92de/createtoken.diff)
Looks good. Just curious, what this test was created for? Have you implemented NtCreateToken() as well? How should I proceed with my implementation?
On Wed Aug 16 11:00:21 2023 +0000, Dmitry Timoshkov wrote:
I dug up my tests for NtCreateToken() from a backup and rebased them.
We may not want them in Wine because of the manual steps required but they could still be useful for development.
[createtoken.diff](/uploads/367d5d8eed8ed92d5bbce51138af92de/createtoken.diff)
Looks good. Just curious, what this test was created for? Have you implemented NtCreateToken() as well? How should I proceed with my implementation?
I started on it but I never finished it.
You may consider writing the tests using a kernel driver, since that's a bit easier to set up. I did this with the object permanence tests in 26c8fb8ce7.