@@ -222,3 +222,98 @@ func TestOverlayTarAUFSUntar(t *testing.T) {
222222 checkFileMode (t , filepath .Join (dst , "d2" , "f1" ), 0o660 )
223223 checkFileMode (t , filepath .Join (dst , "d3" , WhiteoutPrefix + "f1" ), 0o600 )
224224}
225+
226+ // TestChmodNoSymlinkFallback verifies that the chmod fallback applies modes to
227+ // non-symlink entries, including device nodes on a nodev mount.
228+ //
229+ // Regression test for https://github.com/moby/go-archive/issues/98
230+ // Regression test for https://github.com/moby/moby/issues/53299
231+ func TestChmodNoSymlinkFallback (t * testing.T ) {
232+ for _ , tc := range []struct {
233+ name string
234+ nodev bool
235+ create func (string ) error
236+ broken bool
237+
238+ needsRoot bool
239+ }{
240+ {
241+ name : "regular-file" ,
242+ create : func (p string ) error {
243+ return os .WriteFile (p , nil , 0o600 )
244+ },
245+ },
246+ {
247+ name : "directory" ,
248+ create : func (p string ) error {
249+ return os .Mkdir (p , 0o700 )
250+ },
251+ },
252+ {
253+ name : "fifo" ,
254+ create : func (p string ) error {
255+ return unix .Mkfifo (p , 0o600 )
256+ },
257+ },
258+ {
259+ name : "character-device-on-nodev" ,
260+ nodev : true ,
261+ create : func (p string ) error {
262+ return mknod (p , unix .S_IFCHR | 0o600 , unix .Mkdev (1 , 3 ))
263+ },
264+ needsRoot : true ,
265+ broken : true ,
266+ },
267+ {
268+ name : "block-device-on-nodev" ,
269+ nodev : true ,
270+ create : func (p string ) error {
271+ return mknod (p , unix .S_IFBLK | 0o600 , unix .Mkdev (7 , 0 ))
272+ },
273+ needsRoot : true ,
274+ broken : true ,
275+ },
276+ {
277+ // see https://github.com/moby/moby/issues/53299
278+ name : "ptmx-device" ,
279+ create : func (entryPath string ) error {
280+ return mknod (entryPath , unix .S_IFCHR | 0o666 , unix .Mkdev (5 , 2 ))
281+ },
282+ needsRoot : true ,
283+ broken : true ,
284+ },
285+ } {
286+ t .Run (tc .name , func (t * testing.T ) {
287+ if tc .broken {
288+ t .Skip ("FIXME: fallback cannot open device nodes on nodev mounts" )
289+ }
290+ if tc .needsRoot {
291+ skip .If (t , os .Getuid () != 0 , "requires root" )
292+ skip .If (t , userns .RunningInUserNS (), "requires initial user namespace" )
293+ }
294+
295+ tmpDir := t .TempDir ()
296+ if tc .nodev {
297+ assert .NilError (t , unix .Mount ("tmpfs" , tmpDir , "tmpfs" , unix .MS_NODEV , "" ))
298+ t .Cleanup (func () {
299+ assert .Check (t , unix .Unmount (tmpDir , 0 ))
300+ })
301+ }
302+
303+ entryPath := filepath .Join (tmpDir , tc .name )
304+ assert .NilError (t , tc .create (entryPath ))
305+
306+ parent , err := os .Open (tmpDir )
307+ assert .NilError (t , err )
308+ defer parent .Close ()
309+
310+ // #nosec G115 -- file descriptors fit in int on supported platforms.
311+ err = chmodNoSymlinkFallback (int (parent .Fd ()), tc .name , tc .name , 0o640 )
312+ assert .NilError (t , err , "entryPath: %s" , entryPath )
313+
314+ fi , err := os .Lstat (entryPath )
315+ assert .NilError (t , err )
316+ assert .Equal (t , fi .Mode ().Perm (), os .FileMode (0o640 ))
317+ })
318+ }
319+ }
0 commit comments