6

How do I get Windows to recalculate the inherited permissions of a file?

A tool (Mercurial) created multiple NTFS hardlinks to the same file from different directories. I can see them using fsutil hardlink list. This does not change the ACLs of the file. But I would now like that the file inherits the ACLs from the second parent folder. How can I do this? How can I do it for the entire subtree?

icacls /inheritance:e doesn't seem to help...

The Old New Thing has two articles on inherited permissions and hardlinks:

but doesn't seem to mention how to trigger such a recalculation either.

Peter
  • 171
  • 5

1 Answers1

1

I came across this question when looking for how to trigger permission recalculation, so partly I wanted to leave an answer so the next person could find out how to trigger recalculations, but the question of how ACLs and hardlinks interacted intrigued me, so I ran some experiments.

I'm not an expert on NTFS permissions so it's possible I've missed something. But after seven years, a partial answer is probably better than no answer. I would appreciate corrections from an expert.

The short answer to the narrow question of "how to trigger such a recalculation" is to use the /reset option to icacls:

icacls filename /reset

Add the /t option to get recursion at which point you probably also want /q and /c.

So how does this interact with hardlinks? The answer appears to be that the ACL applied to the file will be the calculated ACL for one path to the file you passed to icacls /reset.

That means, the answer to your question "I would now like that the file inherits the ACLs from the second parent folder" means it depends on whether you meant you would like that:

  1. the file inherits the ACLs from the second parent in addition to the ACLs from the first parent; or
  2. the file inherits the ACLs from the second parent instead of the ACLs from the first parent.

The second option appears to be possible by using icacls /reset with the path from the second parent (maybe with /t). The first option does not appear to be easily possible.

Here are my experiments. In each case I'll give the commands and then a little diagram.

First I created two folders that had different inheritable permissions:

C:\Users\User0\AppData\Local\Temp>md icacls_test

C:\Users\User0\AppData\Local\Temp>cd icacls_test

C:\Users\User0\AppData\Local\Temp\icacls_test>md foo

C:\Users\User0\AppData\Local\Temp\icacls_test>md bar

C:\Users\User0\AppData\Local\Temp\icacls_test>icacls foo /grant User1:(oi)(ci)(rx)
processed file: foo
Successfully processed 1 files; Failed processing 0 files

C:\Users\User0\AppData\Local\Temp\icacls_test>icacls bar /grant User2:(oi)(ci)(rx)
processed file: bar
Successfully processed 1 files; Failed processing 0 files


                             c:\
                              |
                            Users
                              |
                            User0
                         +user0:(oi)(ci)(f)
                              :
                              :
                         icacls_test
                        /           \
                      foo           bar
            +user1:(oi)(ci)(rx)  +user2:(oi)(ci)(rx)

Next I created a file under one of the two directories and, as expected, it inherited the permissions from its parent:

C:\Users\User0\AppData\Local\Temp\icacls_test>cd foo

C:\Users\User0\AppData\Local\Temp\icacls_test\foo>copy con: baz.txt
Baz
^Z
        1 file(s) copied.

C:\Users\User0\AppData\Local\Temp\icacls_test\foo>icacls baz.txt
baz.txt ThisMachine\User1:(I)(RX)
        NT AUTHORITY\SYSTEM:(I)(F)
        BUILTIN\Administrators:(I)(F)
        ThisMachine\User0:(I)(F)

Successfully processed 1 files; Failed processing 0 files

                             c:\
                              |
                            Users
                              |
                            User0
                         +user0:(oi)(ci)(f)
                              :
                              :
                         icacls_test
                        /           \
                      foo           bar
            +user1:(oi)(ci)(rx)  +user2:(oi)(ci)(rx)
                       |
                    baz.txt
            user0:(f) user1(rx)

Next I created a hardlink from the second directory to that file. As reported by the person who asked the question, the permissions on the file are not changed. The file keeps the permissions it inherited from the first directory when it was created.

C:\Users\User0\AppData\Local\Temp\icacls_test\foo>cd ..

C:\Users\User0\AppData\Local\Temp\icacls_test>cd bar

C:\Users\User0\AppData\Local\Temp\icacls_test\bar>fsutil hardlink create link.txt c:\Users\User0\AppData\Local\Temp\icacls_test\foo\baz.txt
Hardlink created for C:\Users\User0\AppData\Local\Temp\icacls_test\bar\link.txt <<===>> c:\Users\User0\AppData\Local\Temp\icacls_test\foo\baz.txt

C:\Users\User0\AppData\Local\Temp\icacls_test\bar>icacls link.txt
link.txt ThisMachine\User1:(I)(RX)
         NT AUTHORITY\SYSTEM:(I)(F)
         BUILTIN\Administrators:(I)(F)
         ThisMachine\User0:(I)(F)

Successfully processed 1 files; Failed processing 0 files


                             c:\
                              |
                            Users
                              |
                            User0
                         +user0:(oi)(ci)(f)
                              :
                              :
                         icacls_test
                        /           \
                      foo           bar
            +user1:(oi)(ci)(rx)  +user2:(oi)(ci)(rx)
                       |               |
                    baz.txt        link.txt
                        \             /
                         \           /
                          \         /
                      user0:(f) user1:(rx)

If I now apply an icacls /reset using the new path to the file, the ACL is recalculated using permissions inherited down that path (that is, from the second directory) and these overwrite the current ACL. This means permissions inherited from the first directory are lost:

C:\Users\User0\AppData\Local\Temp\icacls_test\bar>icacls link.txt /reset
processed file: link.txt
Successfully processed 1 files; Failed processing 0 files

C:\Users\User0\AppData\Local\Temp\icacls_test\bar>cd ..

C:\Users\User0\AppData\Local\Temp\icacls_test>icacls *.txt /t
bar\link.txt ThisMachine\User2:(I)(RX)
             NT AUTHORITY\SYSTEM:(I)(F)
             BUILTIN\Administrators:(I)(F)
             ThisMachine\User0:(I)(F)

foo\baz.txt ThisMachine\User2:(I)(RX)
            NT AUTHORITY\SYSTEM:(I)(F)
            BUILTIN\Administrators:(I)(F)
            ThisMachine\User0:(I)(F)

Successfully processed 2 files; Failed processing 0 files


                             c:\
                              |
                            Users
                              |
                            User0
                         +user0:(oi)(ci)(f)
                              :
                              :
                         icacls_test
                        /           \
                      foo           bar
            +user1:(oi)(ci)(rx)  +user2:(oi)(ci)(rx)
                       |               |
                    baz.txt        link.txt
                        \             /
                         \           /
                          \         /
                      user0:(f) user2:(rx)

As you'd expect, applying an icacls /reset to the first path has the opposite effect: permissions inherited from the first directory are restored and those inherited from the second directory are lost.

C:\Users\User0\AppData\Local\Temp\icacls_test>icacls foo\baz.txt /reset
processed file: foo\baz.txt
Successfully processed 1 files; Failed processing 0 files

C:\Users\User0\AppData\Local\Temp\icacls_test>icacls *.txt /t
bar\link.txt ThisMachine\User1:(I)(RX)
             NT AUTHORITY\SYSTEM:(I)(F)
             BUILTIN\Administrators:(I)(F)
             ThisMachine\User0:(I)(F)

foo\baz.txt ThisMachine\User1:(I)(RX)
            NT AUTHORITY\SYSTEM:(I)(F)
            BUILTIN\Administrators:(I)(F)
            ThisMachine\User0:(I)(F)

Successfully processed 2 files; Failed processing 0 files


                             c:\
                              |
                            Users
                              |
                            User0
                         +user0:(oi)(ci)(f)
                              :
                              :
                         icacls_test
                        /           \
                      foo           bar
            +user1:(oi)(ci)(rx)  +user2:(oi)(ci)(rx)
                       |               |
                    baz.txt        link.txt
                        \             /
                         \           /
                          \         /
                      user0:(f) user1:(rx)

If I try to issue one icacls command that targets both files, such as icacls *.txt /t /reset then it still doesn't work. I get one of the two sets of permissions (either user1 or user2 but not both). Which one depends on the order in which icacls processes the paths.