bpo-31512: Add non-elevated symlink support for dev mode Windows 10 by vidartf · Pull Request #3652 · python/cpython (original) (raw)
It just occurred to me that it's potentially a breaking change if Python stops enabling SeCreateSymbolicLinkPrivilege in the process token. When a server impersonates a client, it uses a duplicate of the client's token that honors the client's requested security quality of service. If the client requests that the impersonation is only "effective", which is the default SQOS setting, then the groups and privileges that are currently disabled in the client token will be removed from the server's impersonation token. Note that removing a group or privilege from a token is permanent; it's not merely disabled.
Here's an example that demonstrates the current behavior with a named pipe. It lists the privileges in the process and thread tokens after calling ImpersonateNamedPipeClient
.
from win32pipe import *
from win32file import *
from win32security import *
from win32api import GetCurrentProcess, GetCurrentThread
from win32con import SECURITY_SQOS_PRESENT
hPipeServer = CreateNamedPipe(r'\\?\PIPE\SpamPipe', PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 512, 512, 0, None)
hPipeClient = CreateFile(r'\\?\PIPE\SpamPipe', GENERIC_READ | GENERIC_WRITE,
0, None, OPEN_EXISTING, SECURITY_SQOS_PRESENT |
SECURITY_IMPERSONATION | SECURITY_EFFECTIVE_ONLY, None)
WriteFile(hPipeClient, b'Piped Spam')
ReadFile(hPipeServer, 10)
ImpersonateNamedPipeClient(hPipeServer)
hTokenProcess = OpenProcessToken(GetCurrentProcess(), GENERIC_READ)
print('Client Privileges', '=' * 20,
*sorted(LookupPrivilegeName(None, p[0]) +
(' [ENABLED]' if p[1] & SE_PRIVILEGE_ENABLED else '')
for p in GetTokenInformation(hTokenProcess, TokenPrivileges)),
sep='\n')
hTokenThread = OpenThreadToken(GetCurrentThread(), GENERIC_READ, True)
print('\nServer Privileges', '=' * 20,
*sorted(LookupPrivilegeName(None, p[0]) +
(' [ENABLED]' if p[1] & SE_PRIVILEGE_ENABLED else '')
for p in GetTokenInformation(hTokenThread, TokenPrivileges)),
sep='\n')
Output:
Client Privileges
====================
SeBackupPrivilege
SeChangeNotifyPrivilege [ENABLED]
SeCreateGlobalPrivilege [ENABLED]
SeCreatePagefilePrivilege
SeCreateSymbolicLinkPrivilege [ENABLED]
SeDebugPrivilege
SeDelegateSessionUserImpersonatePrivilege
SeImpersonatePrivilege [ENABLED]
SeIncreaseBasePriorityPrivilege
SeIncreaseQuotaPrivilege
SeIncreaseWorkingSetPrivilege
SeLoadDriverPrivilege
SeLockMemoryPrivilege
SeManageVolumePrivilege
SeProfileSingleProcessPrivilege
SeRemoteShutdownPrivilege
SeRestorePrivilege
SeSecurityPrivilege
SeShutdownPrivilege
SeSystemEnvironmentPrivilege
SeSystemProfilePrivilege
SeSystemtimePrivilege
SeTakeOwnershipPrivilege
SeTimeZonePrivilege
SeUndockPrivilege
Server Privileges
====================
SeChangeNotifyPrivilege [ENABLED]
SeCreateGlobalPrivilege [ENABLED]
SeCreateSymbolicLinkPrivilege [ENABLED]
SeImpersonatePrivilege [ENABLED]
SeCreateSymbolicLinkPrivilege is just one of many privileges that are disabled by default and normally removed in this impersonation context. I'm comfortable with changing this because the docs never explicitly stated that Python enables this privilege for the entire process, only that the privilege is required to create symbolic links.