microsoft/Windows-Containers

`ERROR_ACCESS_DENIED` when trying to open for writing _hidden_ files located in mounted folder inside containers

conioh opened this issue · 8 comments

Describe the bug

From within a container with a mounted host folder, trying to open a hidden file for write access fails with ERROR_ACCESS_DENIED.
Opening and writing succeeds after removing the hidden attribute, without making any changes to the security descriptor.

To Reproduce

Steps to reproduce the behavior:

[E:\]
> mkdir temp

        Directory: E:\


Mode                 LastWriteTime       FileSize Name
----                 -------------       -------- ----
d----          2024-01-07    21:53                temp

[E:\]
> docker run -it --name=foo --isolation=process --mount "type=bind,src=E:\temp,dst=C:\foo" mcr.microsoft.com/windows/servercore:10.0.20348.1787
Microsoft Windows [Version 10.0.22631.1787]
(c) Microsoft Corporation. All rights reserved.

C:\>cd foo

C:\foo>dir
 Volume in drive C has no label.
 Volume Serial Number is XXXX-XXXX

 Directory of C:\foo

01/07/2024  09:53 PM    <DIR>          .
               0 File(s)              0 bytes
               1 Dir(s)  111,222,333,444 bytes free

C:\foo>echo hi > a.txt

C:\foo>type a.txt
hi

C:\foo>attrib +h a.txt

C:\foo>type a.txt
hi

C:\foo>echo hello > a.txt
Access is denied.

C:\foo>exit
[E:\]
> cat E:\temp\a.txt
hi
[E:\]
> Set-Content -Path "E:\temp\a.txt" -Value "hellooooo"
[E:\]
> cat E:\temp\a.txt
hellooooo
[E:\]
> docker start -ai foo
Microsoft Windows [Version 10.0.22631.1787]
(c) Microsoft Corporation. All rights reserved.

C:\>cd foo

C:\foo>type a.txt
hellooooo

C:\foo>echo why > a.txt
Access is denied.

C:\foo>attrib -h a.txt

C:\foo>echo why > a.txt

C:\foo>type a.txt
why

C:\foo>

Or with CmDiag.exe:

[E:\temp]
> cat .\a.txt
why
[E:\temp]
> CmDiag.exe CreateContainer -Type ServerSilo -Id 11111111-1111-1111-1111-111111111111 -FriendlyName Foo
The container was successfully created. Its ID is: 11111111-1111-1111-1111-111111111111
The container will continue running until it is terminated. A new instance of cmdiag has been spun
up in the background to keep the container alive.

[E:\temp]
> CmDiag.exe Map 11111111-1111-1111-1111-111111111111 "E:\temp\" "C:\target"
[E:\temp]
> CmDiag.exe Console 11111111-1111-1111-1111-111111111111 cmd.exe
Executing: cmd.exe
Microsoft Windows [Version 10.0.22631.2861]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\System32>cd c:\target

c:\target>type a.txt
why

c:\target>echo works > a.txt

c:\target>type a.txt
works

c:\target>attrib +h a.txt

c:\target>type a.txt
works

c:\target>echo why > a.txt
Access is denied.

c:\target>exit
[E:\temp]
> Set-Content -PassThru ".\a.txt" -Value "why???"
why???
[E:\temp]
> CmDiag.exe Console 11111111-1111-1111-1111-111111111111 cmd.exe
Executing: cmd.exe
Microsoft Windows [Version 10.0.22631.2861]
(c) Microsoft Corporation. All rights reserved.

C:\Windows\System32>type C:\target\a.txt
why???

C:\Windows\System32>

Expected behavior

I expect to be able to write from within a container to hidden files located in a mounted folder.

Configuration:

  • Edition: Windows 11 Version 23H2 Enterprise (22631.2861)
  • Base Image being used: Any
  • Container engine: Any ("Docker Engine", CmDiag.exe, etc.)
  • Container Engine version: Any (23.0.8, 22631.2861, etc.)

Additional context

Apparently happens only when trying to open the file with CREATE_ALWAYS. OPEN_EXISTING, OPEN_ALWAYS and TRUNCATE_EXISTING succeed.

Hi. Thanks for bringing up this problem. I'll try to reproduce it because it's a very interesting one. Could you check what permissions you have in the container? Are you containerUser or containerAdministrator, etc.?

@ntrappe-msft, since I'm using the mcr.microsoft.com/windows/servercore:10.0.20348.1787 image as it comes from Microsoft, without modifications, I'm running as ContainerAdministrator.

C:\foo>whoami /user

USER INFORMATION
----------------

User Name                           SID
=================================== ============
user manager\containeradministrator S-1-5-93-2-1

C:\foo>

But it also happens with ContainerUser. It's probably not related to security in the regular sense (e.g. things in the security descriptor, SeAccessCheck, etc.) but rather some strange behavior in bindflt.sys.

For completeness, if I run docker --user ContainerUser <...> I still get:

Microsoft Windows [Version 10.0.22631.1787]
(c) Microsoft Corporation. All rights reserved.

C:\>cd foo

C:\foo>type a.txt
hi

C:\foo>attrib +h a.txt

C:\foo>echo foo > a.txt
Access is denied.

C:\foo>whoami /user

USER INFORMATION
----------------

User Name                  SID
========================== ============
user manager\containeruser S-1-5-93-2-2

C:\foo>

Ok so I was able to successfully reproduce your Issue. Even though the container created and set attributes of the file, once the host has modified the file's contents, the container can only see but not change its contents. Interestingly, containerAdministrator has full permissions to read/write that file and the file has no access restrictions. I'm going to keep digging through the logs to see if a method or property of the file did change throughout this process.

once the host has modified the file's contents, the container can only see but not change its contents.

@ntrappe-msft, I don't understand how you got there. In my reproduction the container creates the file, the container sets the hidden attribute and immediately the container can't write again to the file. The container host did not modify the file up to that point. Here's an annotated copy of the reproduction I provided when I opened the issue:

### Here we create an empty directory on the host:

[E:\]
> mkdir temp

        Directory: E:\


Mode                 LastWriteTime       FileSize Name
----                 -------------       -------- ----
d----          2024-01-07    21:53                temp

### The host did not modify the problematic file in this step.

### Here we run Docker and mount the directory we've just created:

[E:\]
> docker run -it --name=foo --isolation=process --mount "type=bind,src=E:\temp,dst=C:\foo" mcr.microsoft.com/windows/servercore:10.0.20348.1787
Microsoft Windows [Version 10.0.22631.1787]
(c) Microsoft Corporation. All rights reserved.

### The host did not modify the problematic file in this step.

### Now we're inside the container and change the current directory
### and get a directory listing:

C:\>cd foo

C:\foo>dir
 Volume in drive C has no label.
 Volume Serial Number is XXXX-XXXX

 Directory of C:\foo

01/07/2024  09:53 PM    <DIR>          .
               0 File(s)              0 bytes
               1 Dir(s)  111,222,333,444 bytes free

### The host did not modify the problematic file in this step.

### Now, INSIDE THE CONTAINER, we create the file using the cmd.exe
### command echo and output redirection:

C:\foo>echo hi > a.txt

### The HOST did not modify the problematic file in this step. We did that
### from inside the CONTAINER.

### Next we verify the data was written into the file:

C:\foo>type a.txt
hi

### The host did not modify the problematic file in this step.

### Next, FROM WITHIN THE CONTAINER, we set the hidden attribute:

C:\foo>attrib +h a.txt

### The HOST did not modify the problematic file in this step. We did that
### from inside the CONTAINER.

### After setting the hidden attribute, we verify that we can still read the file:

C:\foo>type a.txt
hi

### The host did not modify the problematic file in this step.

### Finally, STILL FROM WITHIN THE CONTAINER, we try to write again to the
### file, this time when it has the hidden attribute set:

C:\foo>echo hello > a.txt
Access is denied.

### And it fails. This is the problem. Note that the HOST did not modify the file
### at any point until now.

### We exit cmd and leave the container context:

C:\foo>exit

### The host did not modify the problematic file in this step.

### ONLY NOW we verify that the container host is able to write to the file, but
### this is AFTER we've demonstrated the problem, and the problem doesn't not
### depend on modifying the file from the container host side.

[E:\]
> cat E:\temp\a.txt
hi
[E:\]
> Set-Content -Path "E:\temp\a.txt" -Value "hellooooo"
[E:\]
> cat E:\temp\a.txt
hellooooo
[E:\]
> docker start -ai foo
Microsoft Windows [Version 10.0.22631.1787]
(c) Microsoft Corporation. All rights reserved.

C:\>cd foo

C:\foo>type a.txt
hellooooo

C:\foo>echo why > a.txt
Access is denied.

C:\foo>attrib -h a.txt

C:\foo>echo why > a.txt

C:\foo>type a.txt
why

C:\foo>

Thanks for clarifying. I've identified the exceptions being thrown when a container is trying to write to a hidden file. I'm going to continue to investigate which file system filter is throwing the error and how we can mitigate this.

The ERROR_ACCESS_DENIED is by design. Neither on the host nor in a container can you use echo to overwrite the contents of a hidden file. This was confirmed by the file system team. I've listed the following commands and the results I got on both the host 1, a normal container 2, and a mounted container 3.

Command Host (Win 11) Container Not Mounted Container Mounted
echo hi > a.txt Succeeds Succeeds Succeeds
attrib +h a.txt Succeeds Succeeds Succeeds
echo hello > a.txt Fails (Access Denied) Fails (Access Denied) Fails (Access Denied)
echo hello >> a.txt Succeeds Succeeds Succeeds
Set-Content -PassThru a.txt -Value "hola" Succeeds Succeeds Succeeds

To clarify, if you'd like to overwrite the contents of a hidden file, use Set-Content. But you cannot use echo <value> > <filename>.

Footnotes

  1. Tested on Windows 11 and Windows Server 2022.

  2. Normal container: docker run -it mcr.microsoft.com/windows/servercore:ltsc2022 powershell (by default it is process-isolated).

  3. Mounted container: docker run --mount "type=bind,src=C:\bar,dst=C:\foo" -it mcr.microsoft.com/windows/servercore:ltsc2022 powershell

More details for those who are curious:

  • echo hello > a.txt fails because it attempts to open a file for overwriting
  • We are not allowed to open a hidden file for overwriting so we get ERROR_ACCESS_DENIED
  • Set-Content succeeds because it opens a file normally (not for overwriting similar to how appends opens)

Closing for now but let us know if you have more questions.