@@ -108,35 +108,6 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err
108108
109109 // Make sure the mode doesn't have any type bits.
110110 mode &^= unix .S_IFMT
111- // What properties do we expect any newly created directories to have?
112- var (
113- // While umask(2) is a per-thread property, and thus this value could
114- // vary between threads, a functioning Go program would LockOSThread
115- // threads with different umasks and so we don't need to LockOSThread
116- // for this entire mkdirat loop (if we are in the locked thread with a
117- // different umask, we are already locked and there's nothing for us to
118- // do -- and if not then it doesn't matter which thread we run on and
119- // there's nothing for us to do).
120- expectedMode = uint32 (unix .S_IFDIR | (mode &^ getUmask ()))
121-
122- // We would want to get the fs[ug]id here, but we can't access those
123- // from userspace. In practice, nobody uses setfs[ug]id() anymore, so
124- // just use the effective [ug]id (which is equivalent to the fs[ug]id
125- // for programs that don't use setfs[ug]id).
126- expectedUid = uint32 (unix .Geteuid ())
127- expectedGid = uint32 (unix .Getegid ())
128- )
129-
130- // The setgid bit (S_ISGID = 0o2000) is inherited to child directories and
131- // affects the group of any inodes created in said directory, so if the
132- // starting directory has it set we need to adjust our expected mode and
133- // owner to match.
134- if st , err := fstatFile (currentDir ); err != nil {
135- return nil , fmt .Errorf ("failed to stat starting path for mkdir %q: %w" , currentDir .Name (), err )
136- } else if st .Mode & unix .S_ISGID == unix .S_ISGID {
137- expectedMode |= unix .S_ISGID
138- expectedGid = st .Gid
139- }
140111
141112 // Create the remaining components.
142113 for _ , part := range remainingParts {
@@ -175,35 +146,33 @@ func MkdirAllHandle(root *os.File, unsafePath string, mode int) (_ *os.File, Err
175146 _ = currentDir .Close ()
176147 currentDir = nextDir
177148
178- // Make sure that the directory matches what we expect. An attacker
179- // could have swapped the directory between us making it and opening
180- // it. There's no way for us to be sure that the directory is
181- // _precisely_ the same as the directory we created, but if we are in
182- // an empty directory with the same owner and mode as the one we
183- // created then there is nothing the attacker could do with this new
184- // directory that they couldn't do with the old one.
185- if stat , err := fstat (currentDir ); err != nil {
186- return nil , fmt .Errorf ("check newly created directory: %w" , err )
187- } else {
188- if stat .Mode != expectedMode {
189- return nil , fmt .Errorf ("%w: newly created directory %q has incorrect mode 0o%.3o (expected 0o%.3o)" , errPossibleAttack , currentDir .Name (), stat .Mode , expectedMode )
190- }
191- if stat .Uid != expectedUid || stat .Gid != expectedGid {
192- return nil , fmt .Errorf ("%w: newly created directory %q has incorrect owner %d:%d (expected %d:%d)" , errPossibleAttack , currentDir .Name (), stat .Uid , stat .Gid , expectedUid , expectedGid )
193- }
194- // Check that the directory is empty. We only need to check for
195- // a single entry, and we should get EOF if the directory is
196- // empty.
197- _ , err := currentDir .Readdirnames (1 )
198- if ! errors .Is (err , io .EOF ) {
199- if err == nil {
200- err = fmt .Errorf ("%w: newly created directory %q is non-empty" , errPossibleAttack , currentDir .Name ())
201- }
202- return nil , fmt .Errorf ("check if newly created directory %q is empty: %w" , currentDir .Name (), err )
149+ // Try our best to check that the directory is empty and so is unlikely
150+ // to have been swapped by an attacker.
151+ //
152+ // Ideally we would also check that the owner and mode match what we
153+ // would've created -- unfortunately, it is non-trivial to verify that
154+ // the owner and mode of the created directory match. While plain Unix
155+ // DAC rules seem simple enough to emulate, there are a bunch of other
156+ // factors that can change the mode or owner of created directories
157+ // (default POSIX ACLs, mount options like uid=1,gid=2,umask=0 on
158+ // filesystems like vfat, etc etc). We used to try to verify this but
159+ // it just lead to a series of spurious errors.
160+ //
161+ // To be honest, since MkdirAll allows you to use existing directories,
162+ // the practical scope of this protection seems very limited (if it
163+ // even exists) so it really isn't that important.
164+
165+ // We only need to check for a single entry to see if it's empty, and
166+ // we should get EOF if the directory is empty.
167+ _ , err := currentDir .Readdirnames (1 )
168+ if ! errors .Is (err , io .EOF ) {
169+ if err == nil {
170+ err = fmt .Errorf ("%w: newly created directory %q is non-empty" , errPossibleAttack , currentDir .Name ())
203171 }
204- // Reset the offset.
205- _ , _ = currentDir .Seek (0 , unix .SEEK_SET )
172+ return nil , fmt .Errorf ("check if newly created directory %q is empty: %w" , currentDir .Name (), err )
206173 }
174+ // Reset the offset.
175+ _ , _ = currentDir .Seek (0 , unix .SEEK_SET )
207176 }
208177 return currentDir , nil
209178}
0 commit comments