This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Unable to write to file without elevated privileges
Type: behavior Stage: resolved
Components: Windows Versions: Python 3.8
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: eryksun, john_miller, paul.moore, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2020-10-15 17:36 by john_miller, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (6)
msg378690 - (view) Author: (john_miller) Date: 2020-10-15 17:36
Trying to write into a file fails with a PermissionError (PermissionError: [Errno 13] Permission denied:).

Typing in a non-elevated shell (User is Administrator)
C:\Users\Username\Desktop>C:\Python38-32\python.exe
open("some_file.txt","w") fails on the Desktop, in the user directory and in directories where Administrators are permitted to write (while normal users can only read and execute).

(Permissions for the user in those directories: 
Desktop rwx, 
Username-directory rwx, 
third-location r-x for users/rwx for Administrator-user-group (with non-elevated applications))

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?
msg378691 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2020-10-15 18:27
> Desktop rwx, 
> Username-directory rwx, 

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"')
msg378714 - (view) Author: (john_miller) Date: 2020-10-16 14:00
Short summary:
>icacls.exe C:\Python38-32\python.exe lists Mandatory Label\Low Mandatory Level:(I)(NW) ** This might be the problem. Removing "L" with icacls might work.
>User integrity level is usually "Medium"
>**When a user attempts to launch an executable file, the new process is created with the minimum of the user integrity level and the file integrity level.**
>NOT TESTED "icacls.exe" with option "/setintegritylevel Medium" applied to all relevant files in the Python-directory could changee the low integrity-level inherited from "C:\".

---
The python-process runs under the username "Username" according to the 
>whoami.exe (in python.exe launched from a non-elevated console)
COMPUTERNAME\username
0
>whoami.exe (in python.exe launched from an elevated console)
COMPUTERNAME\username
0
>whoami.exe (launched directly from non-elevated console)
COMPUTERNAME\username

>icacls.exe "C:\Users\Username\Desktop" 
C:\Users\Username\Desktop NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
                          BUILTIN\Administrators:(I)(OI)(CI)(F)
                          COMPUTERNAME\Username:(I)(OI)(CI)(F)
>icacls.exe "C:\Users\Username"
C:\Users\Username NT AUTHORITY\SYSTEM:(OI)(CI)(F)
                  BUILTIN\Administrators:(OI)(CI)(F)
                  COMPUTERNAME\Username:(OI)(CI)(F)
                  COMPUTERNAME\HomeUsers:(RX)

(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:
A difference between the processes seems to relate to MIC-labels on the running processes[1], especially when compared to a notepad.exe launched from a non-elevated console.

cmd.exe (Non-elevated) Medium -> python.exe Low
cmd.exe (Elevated) High -> python.exe High
cmd.exe (Non-elevated) Medium -> notepad.exe Medium

Low -> Mandatory Label\Low Mandatory Level
Medium -> Mandatory Label\Medium Mandatory Level
High-> Mandatory Label\High Mandatory Level

It seems this labeling system is checked before Discretionary Access Control List's (DACL) come into play.[2]

>Windows defines four integrity levels: low, medium, high, and system. Standard users receive medium, elevated users receive high. Processes you start and objects you create receive your integrity level (medium or high) or low if the executable file's level is low; system services receive system integrity. Objects that lack an integrity label are treated as medium by the operating system; this prevents low-integrity code from modifying unlabeled objects. Additionally, Windows ensures that processes running with a low integrity level cannot obtain access a process which is associated with an app container.

>**When a user attempts to launch an executable file, the new process is created with the minimum of the user integrity level and the file integrity level.** 
>This means that the new process will never execute with higher integrity than the executable file. If the administrator user executes a low integrity program, the token for the new process functions with the low integrity level. This helps protect a user who launches untrustworthy code from malicious acts performed by that code. The user data, which is at the typical user integrity level, is write-protected against this new process.

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:
>SetACL.exe -on "C:\Windows\system32\notepad.exe" -ot file -actn list -lst "f:tab;w:d,s,o,g;i:y"
for SACL-part: Everyone   write+WRITE_OWNER+WRITE_DAC+DELETE   audit   audit_success+audit_fail
>SetACL.exe -on "C:\Python38-32\python.exe" -ot file -actn list -lst "f:tab;w:d,s,o,g;i:y"
for SACL-part: [empty]


>icacls.exe C:\Python38-32\python.exe BUILTIN\Administrators:(I)(F)
                           NT AUTHORITY\SYSTEM:(I)(F)
                           BUILTIN\User:(I)(RX)
                           NT AUTHORITY\Authenticated Users:(I)(M)
                           Mandatory Label\Low Mandatory Level:(I)(NW) ** This might be the problem. Removing "L" with icacls might work.
>icacls.exe C:\Python38-32
C:\Python38-32 BUILTIN\Administrators:(I)(F)
               BUILTIN\Administrators:(I)(OI)(CI)(IO)(F)
               NT AUTHORITY\SYSTEM:(I)(F)
               NT AUTHORITY\SYSTEM:(I)(OI)(CI)(IO)(F)
               BUILTIN\User:(I)(OI)(CI)(RX)
               NT AUTHORITY\Authenticated Users:(I)(M)
               NT AUTHORITY\Authenticated Users:(I)(OI)(CI)(IO)(M)
               Mandatory Label\Low Mandatory Level:(I)(OI)(CI)(NW)
>icacls.exe C:\
C:\ BUILTIN\Administrators:(F)
    BUILTIN\Administrators:(OI)(CI)(IO)(F)
    NT AUTHORITY\SYSTEM:(F)
    NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)
    BUILTIN\User:(OI)(CI)(RX)
    NT AUTHORITY\Authenticated Users:(OI)(CI)(IO)(M)
    NT AUTHORITY\Authenticated Users:(AD)
    Mandatory Label\Low Mandatory Level:(OI)(CI)(NW)

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.
("dir /R file.txt" shows file.txt:Zone.Identifier:$DATA ; access with "notepad.exe file.txt:Zone.Identifier").
Neither C:\Python38-32 nor C:\Python38-32 have any alternate data-streams attached. But double-clicking on python.exe directly gives a security-warning (probably due to low-integrity-level) talking about how files from the Internet can be dangerous.

(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 )
[2] https://docs.microsoft.com/en-us/windows/win32/secauthz/mandatory-integrity-control
[3] https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-system_mandatory_label_ace
[4] https://docs.microsoft.com/en-us/windows/win32/ad/retrieving-an-objectampaposs-sacl
[5] https://helgeklein.com/setacl/documentation/command-line-version-setacl-exe/
msg378717 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2020-10-16 15:01
> icacls.exe C:\Python38-32\python.exe lists Mandatory Label\
> Low Mandatory Level:(I)(NW) ** This might be the problem. Removing "L"
> with icacls might work.
>
> **When a user attempts to launch an executable file, the new process is
> created with the minimum of the user integrity level and the file 
> integrity level.**

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

> >icacls.exe C:\
> C:\ BUILTIN\Administrators:(F)
>     BUILTIN\Administrators:(OI)(CI)(IO)(F)
>     NT AUTHORITY\SYSTEM:(F)
>     NT AUTHORITY\SYSTEM:(OI)(CI)(IO)(F)
>     BUILTIN\User:(OI)(CI)(RX)
>     NT AUTHORITY\Authenticated Users:(OI)(CI)(IO)(M)
>     NT AUTHORITY\Authenticated Users:(AD)
>     Mandatory Label\Low Mandatory Level:(OI)(CI)(NW)

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):

    Mandatory Label\High Mandatory Level:(OI)(NP)(IO)(NW)

> 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 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
msg378740 - (view) Author: (john_miller) Date: 2020-10-16 19:40
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:
>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:

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:
>**When a user attempts to launch an executable file, the new process is created with the minimum of the user integrity level and the file integrity level.**
As the shell is started with medium integrity level and the file is set to low integrity level the process would get created with low integrity level.

Regarding the integrity settings:
This seems to be problem affecting other people too.
https://answers.microsoft.com/en-us/protect/forum/mse-protect_scanning-windows_7/cs-integrity-level-set-to-low-by-essentials-full/e61e537e-54fb-4923-93bc-784a0b583f1a
https://answers.microsoft.com/en-us/windows/forum/windows_7-winapps/root-of-systemdrive-keeps-getting-low-integrity/6cfd967d-17f5-44a1-beaa-1ad1ffe28faa
https://answers.microsoft.com/en-us/windows/forum/all/root-of-systemdrive-keeps-getting-low-integrity/6cfd967d-17f5-44a1-beaa-1ad1ffe28faa
"C:\Program Files", "C:\Users" and "C:\Windows" seem to have their own DACL's.

(win32security.GetFileSecurity("C:\\", win32security.SACL_SECURITY_INFORMATION) fails on me even on an elevated prompt.
chml https://www.minasi.com/apps/ seems to be more descriptive with SACL-integrity policies (No write up, No read up, No execute up))
(icacls.exe seems to have undocumented options with /setintegritylevel https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
"(NW)" is not directly mentioned. I'm assuming "(NR)" and "(NX)" might be the missing integrity policy options for an integrity level entry.)
msg378785 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2020-10-16 23:10
> processtoken = win32security.OpenProcessToken(process, win32con.MAXIMUM_ALLOWED)
> win32security.GetTokenInformation(processtoken, win32security.TokenMandatoryPolicy)

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).

> As the shell is started with medium integrity level and the file is set to low 
> integrity level the process would get created with low integrity level.

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.

> "C:\Program Files", "C:\Users" and "C:\Windows" seem to have their own DACL's.

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.

> win32security.GetFileSecurity("C:\\", win32security.SACL_SECURITY_INFORMATION) 
> fails on me even on an elevated prompt.

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).

> "(NW)" is not directly mentioned. I'm assuming "(NR)" and "(NX)" might be the 
> missing integrity policy options for an integrity level entry.

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
History
Date User Action Args
2022-04-11 14:59:36adminsetgithub: 86212
2020-10-16 23:10:58eryksunsetstatus: open -> closed
resolution: not a bug
messages: + msg378785

stage: resolved
2020-10-16 19:40:08john_millersetmessages: + msg378740
2020-10-16 15:01:11eryksunsetmessages: + msg378717
2020-10-16 14:00:55john_millersetmessages: + msg378714
2020-10-15 18:27:59eryksunsetnosy: + eryksun
messages: + msg378691
2020-10-15 17:36:32john_millercreate