New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to write to file without elevated privileges #86212
Comments
Trying to write into a file fails with a PermissionError (PermissionError: [Errno 13] Permission denied:). Typing in a non-elevated shell (User is Administrator) (Permissions for the user in those directories: I can write to all these places without elevation using the shell or standard applications like notepad.exe. Why do I have to elevate the Python-process to access such functionality? Is this related to the security-settings on python.exe or its installation-directory location? Forcing a user to elevate the user-launched process for tasks that operate on normal user-writable files feels like a light security-risk. Because they get used to elevate Python-scripts even if the performed task by itself would not require it. What changed compared to Python 2? |
POSIX permissions are not meaningful in Windows. Please run the following two commands in a Python script in order to show the current user and the access-control list on the directory: import subprocess
subprocess.call('whoami.exe')
subprocess.call(r'icacls.exe "C:\Users\Username\Desktop"') |
Short summary:
---
(I used to UNIX-syntax as a short-hand for specified permissions relating to a specified user. I can see how that could introduce misunderstandings for everyone glancing over the text.) I used procexp.exe to dig around the Security-tab of the active processes: cmd.exe (Non-elevated) Medium -> python.exe Low Low -> Mandatory Label\Low Mandatory Level It seems this labeling system is checked before Discretionary Access Control List's (DACL) come into play.[2]
The file integrity-level can be modified with icacls.exe and it's "/setintegritylevel [(CI)(OI)]Level"-option (Level= one element of {L,M,H,Low,Medium,High}). (In theory "icacls.exe C:\Python38-32\python.exe /setintegritylevel Medium" might do the trick. I haven't tested this. I wonder if icacls would still list an integrity-level on the file for files that have Medium integrity.) I'll also look into System Access Control List's (SACL)[4] too as mentioned in [2]. Using SetACL.exe [5], I found nothing of note:
icacls.exe "C:\Program Files" doesn't have "Mandatory Label\Low Mandatory Level" in it's ACL. It's probably not related, but downloading with Chrome, sets an NTFS-alternate-datastream "Zone.Identifier" with content "[ZoneTransfer]"+line-break+"ZoneId=3" on files it downloads as of late. (Internet Explorer might change it's own integrity level downwards once launched, because the file-integrity-level of the exe is not low.) [1] https://en.wikipedia.org/wiki/Mandatory_Integrity_Control ) |
The token mandatory policy [1] for a standard logon is TOKEN_MANDATORY_POLICY_NO_WRITE_UP (1) and TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN (2). The above quote applies to the latter. For an elevated logon, the mandatory policy is just TOKEN_MANDATORY_POLICY_NO_WRITE_UP, so setting a low-integrity label on python.exe has no effect on a new process created from an elevated security context. The following queries demonstrate the mandatory policy for both cases: standard logon: >>> GetTokenInformation(-4, TokenMandatoryPolicy)
3 elevated logon: >>> GetTokenInformation(-4, TokenMandatoryPolicy)
1
Something has modified the security on the root directory of your system drive. The low-integrity no-write-up (NW) label that's inheritable by directories (CI) and files (OI) is the source of the problem. It's supposed to be a high-integrity no-write-up (NW) label that applies to files in the root directory (OI)(NP) and not to the root directory itself (IO) or subdirectories (no CI):
I was concerned that you were using a third-party tools such as MSYS2 bash to check permissions. POSIX rwx access for a user can be computed in terms of effective permissions and generic read, write, and execute access rights. But there's no equivalent to POSIX owner and group permissions. Access for a user SID has to be computed against all entries in the DACL and the mandatory label. [1] https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-token_mandatory_policy |
I changed the integrity-level of "C:\" to "Mandatory Label\High Mandatory Level:(OI)(NP)(IO)(NW)" which seems to have fixed the problem. Thanks for the help. I guess I must have directly or through some other application indirectly changed the integrity level of "C:\". Regarding:
Could this be affected by User-Account-Control (UAC) being set to the highest level? Starting python.exe from a non-elevated shell (user is administrator):
>>> import win32security
>>> import win32api
>>> import win32con
>>> process = win32api.GetCurrentProcess()
>>> processtoken = win32security.OpenProcessToken(process, win32con.MAXIMUM_ALLOWED)
>>> win32security.GetTokenInformation(processtoken, win32security.TokenMandatoryPolicy)
3 (TOKEN_MANDATORY_POLICY_NO_WRITE_UP and TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN)
Starting python.exe from an elevated shell (user is administrator):
>>> import win32security
>>> import win32api
>>> import win32con
>>> process = win32api.GetCurrentProcess()
>>> processtoken = win32security.OpenProcessToken(process, win32con.MAXIMUM_ALLOWED)
>>> win32security.GetTokenInformation(processtoken, win32security.TokenMandatoryPolicy)
1 (TOKEN_MANDATORY_POLICY_NO_WRITE_UP) I assume in this case the following sentence would apply with the "python.exe"-file's integrity level being set to Low:
Regarding the integrity settings: (win32security.GetFileSecurity("C:\\", win32security.SACL_SECURITY_INFORMATION) fails on me even on an elevated prompt. |
FYI, starting with Windows 8, the system supports pseudo-handles for the access token of the current process -- (HANDLE)-4 -- and the current thread -- (HANDLE)-5, which don't have to be opened and closed. In the API, they're available as the inlined functions GetCurrentProcessToken() and GetCurrentThreadToken(). These pseudo-handles have TOKEN_QUERY and TOKEN_QUERY_SOURCE access, so they can be used with token queries, i.e. GetTokenInformation(-4, TokenInformationClass).
Yes, because the access token of shell, which is a limited medium-integrity logon, has a mandatory policy that includes TOKEN_MANDATORY_POLICY_NEW_PROCESS_MIN.
Those directories have protected DACLs with custom security, so they don't inherit the inheritable entries from the root directory. For example: >>> sd = GetNamedSecurityInfo(r'C:\Program Files', SE_FILE_OBJECT,
... DACL_SECURITY_INFORMATION)
>>> sd.GetSecurityDescriptorControl()[0] & SE_DACL_PROTECTED
4096 That said, Python's installer doesn't set custom security on the installation directory, and that's not likely to change. It just relies on inheritance. If you install in "C:\Python38-32", and the inheritable security from the root directory is problematic, then you need to resolve the problem manually, as you have done.
Querying audit entries in the SACL of an object (SACL_SECURITY_INFORMATION) requires ACCESS_SYSTEM_SECURITY access, which requires SeSecurityPrivilege to be enabled. Administrators have this privilege, but it's disabled by default. Some entries in the SACL can be read with just READ_CONTROL access: the mandatory label (LABEL_SECURITY_INFORMATION -- WRITE_OWNER access to set), security resource attributes (ATTRIBUTE_SECURITY_INFORMATION -- WRITE_DAC access to set), and the central access policy identifier (SCOPE_SECURITY_INFORMATION -- ACCESS_SYSTEM_SECURITY access to set).
I don't think icacls.exe allows setting no-read-up and no-execute-up access control. "NR" and "NX" appear to be ignored. For example: >>> cmd = r'icacls C:\Temp\spam.txt /setintegritylevel H:(NW)(NR)(NX)'
>>> subprocess.call(cmd)
processed file: C:\Temp\spam.txt
Successfully processed 1 files; Failed processing 0 files
0
>>> sd = GetNamedSecurityInfo(r'C:\Temp\spam.txt', SE_FILE_OBJECT,
... LABEL_SECURITY_INFORMATION)
>>> sacl = sd.GetSecurityDescriptorSacl()
>>> (acetype, aceflags), mask, sid = sacl.GetAce(0)
>>> acetype == SYSTEM_MANDATORY_LABEL_ACE_TYPE
True
>>> aceflags == 0
True
>>> LookupAccountSid(None, sid)
('High Mandatory Level', 'Mandatory Label', 10) But only the no-write-up access control is set: >>> mask == SYSTEM_MANDATORY_LABEL_NO_WRITE_UP
True |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: