From 2a1064c5f90beac2bd709ab5d1c454c90a16189b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michael=20M=C3=BCller?= <michael@fds-team.de>
Date: Fri, 4 Aug 2017 02:51:57 +0200
Subject: advapi32: Implement CreateRestrictedToken.

---
 dlls/advapi32/security.c       | 88 +++++++++++++++++++++++++++++++++++-------
 dlls/advapi32/tests/security.c | 88 +++++++++++++++++++++++++++++++++++++++---
 2 files changed, 157 insertions(+), 19 deletions(-)

diff --git a/dlls/advapi32/security.c b/dlls/advapi32/security.c
index 82bb6689d43..c531e45c9a0 100644
--- a/dlls/advapi32/security.c
+++ b/dlls/advapi32/security.c
@@ -840,6 +840,60 @@ BOOL WINAPI SetThreadToken(PHANDLE thread, HANDLE token)
                                                  ThreadImpersonationToken, &token, sizeof token ));
 }
 
+static BOOL allocate_groups(TOKEN_GROUPS **groups_ret, SID_AND_ATTRIBUTES *sids, DWORD count)
+{
+    TOKEN_GROUPS *groups;
+    DWORD i;
+
+    if (!count)
+    {
+        *groups_ret = NULL;
+        return TRUE;
+    }
+
+    groups = (TOKEN_GROUPS *)heap_alloc(FIELD_OFFSET(TOKEN_GROUPS, Groups) +
+                                        count * sizeof(SID_AND_ATTRIBUTES));
+    if (!groups)
+    {
+        SetLastError(ERROR_OUTOFMEMORY);
+        return FALSE;
+    }
+
+    groups->GroupCount = count;
+    for (i = 0; i < count; i++)
+        groups->Groups[i] = sids[i];
+
+    *groups_ret = groups;
+    return TRUE;
+}
+
+static BOOL allocate_privileges(TOKEN_PRIVILEGES **privileges_ret, LUID_AND_ATTRIBUTES *privs, DWORD count)
+{
+    TOKEN_PRIVILEGES *privileges;
+    DWORD i;
+
+    if (!count)
+    {
+        *privileges_ret = NULL;
+        return TRUE;
+    }
+
+    privileges = (TOKEN_PRIVILEGES *)heap_alloc(FIELD_OFFSET(TOKEN_PRIVILEGES, Privileges) +
+                                                count * sizeof(LUID_AND_ATTRIBUTES));
+    if (!privileges)
+    {
+        SetLastError(ERROR_OUTOFMEMORY);
+        return FALSE;
+    }
+
+    privileges->PrivilegeCount = count;
+    for (i = 0; i < count; i++)
+        privileges->Privileges[i] = privs[i];
+
+    *privileges_ret = privileges;
+    return TRUE;
+}
+
 /*************************************************************************
  * CreateRestrictedToken [ADVAPI32.@]
  *
@@ -871,25 +925,33 @@ BOOL WINAPI CreateRestrictedToken(
     PSID_AND_ATTRIBUTES restrictSids,
     PHANDLE newToken)
 {
-    TOKEN_TYPE type;
-    SECURITY_IMPERSONATION_LEVEL level = SecurityAnonymous;
-    DWORD size;
+    TOKEN_PRIVILEGES *delete_privs = NULL;
+    TOKEN_GROUPS *disable_groups = NULL;
+    TOKEN_GROUPS *restrict_sids = NULL;
+    BOOL ret = FALSE;
 
-    FIXME("(%p, 0x%x, %u, %p, %u, %p, %u, %p, %p): stub\n",
+    TRACE("(%p, 0x%x, %u, %p, %u, %p, %u, %p, %p)\n",
           baseToken, flags, nDisableSids, disableSids,
           nDeletePrivs, deletePrivs,
           nRestrictSids, restrictSids,
           newToken);
 
-    size = sizeof(type);
-    if (!GetTokenInformation( baseToken, TokenType, &type, size, &size )) return FALSE;
-    if (type == TokenImpersonation)
-    {
-        size = sizeof(level);
-        if (!GetTokenInformation( baseToken, TokenImpersonationLevel, &level, size, &size ))
-            return FALSE;
-    }
-    return DuplicateTokenEx( baseToken, MAXIMUM_ALLOWED, NULL, level, type, newToken );
+    if (!allocate_groups(&disable_groups, disableSids, nDisableSids))
+        goto done;
+
+    if (!allocate_privileges(&delete_privs, deletePrivs, nDeletePrivs))
+        goto done;
+
+    if (!allocate_groups(&restrict_sids, restrictSids, nRestrictSids))
+        goto done;
+
+    ret = set_ntstatus(NtFilterToken(baseToken, flags, disable_groups, delete_privs, restrict_sids, newToken));
+
+done:
+    heap_free(disable_groups);
+    heap_free(delete_privs);
+    heap_free(restrict_sids);
+    return ret;
 }
 
 /*	##############################
diff --git a/dlls/advapi32/tests/security.c b/dlls/advapi32/tests/security.c
index a1ecc409b73..0fd41fe82fa 100644
--- a/dlls/advapi32/tests/security.c
+++ b/dlls/advapi32/tests/security.c
@@ -5292,10 +5292,13 @@ static void test_GetUserNameW(void)
 
 static void test_CreateRestrictedToken(void)
 {
+    TOKEN_PRIMARY_GROUP *primary_group, *primary_group2;
     HANDLE process_token, token, r_token;
     PTOKEN_GROUPS token_groups, groups2;
     SID_AND_ATTRIBUTES sattr;
     SECURITY_IMPERSONATION_LEVEL level;
+    TOKEN_PRIVILEGES *privs;
+    PRIVILEGE_SET privset;
     TOKEN_TYPE type;
     BOOL is_member;
     DWORD size;
@@ -5311,7 +5314,7 @@ static void test_CreateRestrictedToken(void)
     ret = OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE|TOKEN_QUERY, &process_token);
     ok(ret, "got error %d\n", GetLastError());
 
-    ret = DuplicateTokenEx(process_token, TOKEN_DUPLICATE|TOKEN_ADJUST_GROUPS|TOKEN_QUERY,
+    ret = DuplicateTokenEx(process_token, TOKEN_DUPLICATE|TOKEN_ADJUST_GROUPS|TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
         NULL, SecurityImpersonation, TokenImpersonation, &token);
     ok(ret, "got error %d\n", GetLastError());
 
@@ -5342,11 +5345,21 @@ static void test_CreateRestrictedToken(void)
     ok(ret, "got error %d\n", GetLastError());
     ok(is_member, "not a member\n");
 
-    /* disable a SID in new token */
+    privset.PrivilegeCount = 1;
+    privset.Control = PRIVILEGE_SET_ALL_NECESSARY;
+    ret = LookupPrivilegeValueA(NULL, "SeChangeNotifyPrivilege", &privset.Privilege[0].Luid);
+    ok(ret, "got error %d\n", GetLastError());
+
+    is_member = FALSE;
+    ret = PrivilegeCheck(token, &privset, &is_member);
+    ok(ret, "got error %d\n", GetLastError());
+    ok(is_member, "Expected SeChangeNotifyPrivilege to be enabled\n");
+
+    /* disable a SID and a privilege in new token */
     sattr.Sid = token_groups->Groups[i].Sid;
     sattr.Attributes = 0;
     r_token = NULL;
-    ret = pCreateRestrictedToken(token, 0, 1, &sattr, 0, NULL, 0, NULL, &r_token);
+    ret = pCreateRestrictedToken(token, 0, 1, &sattr, 1, &privset.Privilege[0], 0, NULL, &r_token);
     ok(ret, "got error %d\n", GetLastError());
 
     if (ret)
@@ -5355,7 +5368,7 @@ static void test_CreateRestrictedToken(void)
         is_member = TRUE;
         ret = pCheckTokenMembership(r_token, token_groups->Groups[i].Sid, &is_member);
         ok(ret, "got error %d\n", GetLastError());
-        todo_wine ok(!is_member, "not a member\n");
+        ok(!is_member, "not a member\n");
 
         ret = GetTokenInformation(r_token, TokenGroups, NULL, 0, &size);
         ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n",
@@ -5370,9 +5383,9 @@ static void test_CreateRestrictedToken(void)
                 break;
         }
 
-        todo_wine ok(groups2->Groups[j].Attributes & SE_GROUP_USE_FOR_DENY_ONLY,
+        ok(groups2->Groups[j].Attributes & SE_GROUP_USE_FOR_DENY_ONLY,
             "got wrong attributes\n");
-        todo_wine ok((groups2->Groups[j].Attributes & SE_GROUP_ENABLED) == 0,
+        ok((groups2->Groups[j].Attributes & SE_GROUP_ENABLED) == 0,
             "got wrong attributes\n");
 
         HeapFree(GetProcessHeap(), 0, groups2);
@@ -5386,10 +5399,73 @@ static void test_CreateRestrictedToken(void)
         ret = GetTokenInformation(r_token, TokenImpersonationLevel, &level, size, &size);
         ok(ret, "got error %d\n", GetLastError());
         ok(level == SecurityImpersonation, "got level %u\n", type);
+
+        is_member = TRUE;
+        ret = PrivilegeCheck(r_token, &privset, &is_member);
+        ok(ret, "got error %d\n", GetLastError());
+        ok(!is_member, "Expected SeChangeNotifyPrivilege not to be enabled\n");
+
+        ret = GetTokenInformation(r_token, TokenPrivileges, NULL, 0, &size);
+        ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n",
+            ret, GetLastError());
+        privs = HeapAlloc(GetProcessHeap(), 0, size);
+        ret = GetTokenInformation(r_token, TokenPrivileges, privs, size, &size);
+        ok(ret, "got error %d\n", GetLastError());
+
+        is_member = FALSE;
+        for (j = 0; j < privs->PrivilegeCount; j++)
+        {
+            if (RtlEqualLuid(&privs->Privileges[j].Luid, &privset.Privilege[0].Luid))
+            {
+                is_member = TRUE;
+                break;
+            }
+        }
+
+        ok(!is_member, "Expected not to find privilege\n");
+        HeapFree(GetProcessHeap(), 0, privs);
     }
 
     HeapFree(GetProcessHeap(), 0, token_groups);
     CloseHandle(r_token);
+
+    ret = GetTokenInformation(token, TokenPrimaryGroup, NULL, 0, &size);
+    ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n",
+        ret, GetLastError());
+    primary_group = HeapAlloc(GetProcessHeap(), 0, size);
+    ret = GetTokenInformation(token, TokenPrimaryGroup, primary_group, size, &size);
+    ok(ret, "got error %d\n", GetLastError());
+
+    /* disable primary group */
+    sattr.Sid = primary_group->PrimaryGroup;
+    sattr.Attributes = 0;
+    r_token = NULL;
+    ret = pCreateRestrictedToken(token, 0, 1, &sattr, 0, NULL, 0, NULL, &r_token);
+    ok(ret, "got error %d\n", GetLastError());
+
+    if (ret)
+    {
+        is_member = TRUE;
+        ret = pCheckTokenMembership(r_token, primary_group->PrimaryGroup, &is_member);
+        ok(ret, "got error %d\n", GetLastError());
+        ok(!is_member, "not a member\n");
+
+        ret = GetTokenInformation(r_token, TokenPrimaryGroup, NULL, 0, &size);
+        ok(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER, "got %d with error %d\n",
+            ret, GetLastError());
+        primary_group2 = HeapAlloc(GetProcessHeap(), 0, size);
+        ret = GetTokenInformation(r_token, TokenPrimaryGroup, primary_group2, size, &size);
+        ok(ret, "got error %d\n", GetLastError());
+
+        ok(EqualSid(primary_group2->PrimaryGroup, primary_group->PrimaryGroup),
+           "Expected same primary group\n");
+
+        HeapFree(GetProcessHeap(), 0, primary_group2);
+    }
+
+    HeapFree(GetProcessHeap(), 0, primary_group);
+    CloseHandle(r_token);
+
     CloseHandle(token);
     CloseHandle(process_token);
 }
-- 
2.13.1

