#1008240 Inside mmdebstrap hooks, find /dev/ -type f matches irregular files

Package:
mmdebstrap
Source:
mmdebstrap
Submitter:
"Trent W. Buck"
Date:
2022-03-25 06:18:02 UTC
Severity:
minor
Tags:
#1008240#5
Date:
2022-03-25 04:15:41 UTC
From:
To:
I see a quite odd behaviour where "find ... -type f" inside a customize hook is matching device files.
As a simple test, "find /dev -type f" finds /dev/zero inside mmdebstrap, but not outside mmdebstrap.

The problem doesn't appear to be affecting stat, test, or python -- only find.
I haven't tested with bwrap or unshare(1) instead of mmdebstrap – I'm not sure exactly how.
If find fails for those, clearly this bug should be reassigned to findutils.

I speculate this is some interaction between unshare(2) and stat(2) that may be a bug in find.
I looked at the strace, but I can't see anything obvious.

Here is some basic investigation outside mmdebstrap:

    bash5$ find /dev/ -type f
    [no matches]

    bash5$ strace -e trace=file find /dev/zero -type f -print -quit
    execve("/usr/bin/find", ["find", "/dev/zero", "-type", "f", "-print", "-quit"], 0x7ffc1e9c8578 /* 72 vars */) = 0
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpcre2-8.so.0", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
    statfs("/sys/fs/selinux", 0x7ffd464f5210) = -1 ENOENT (No such file or directory)
    statfs("/selinux", 0x7ffd464f5210)      = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
    access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, ".", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/share/locale/en_AU.UTF-8/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/en_AU.utf8/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/en_AU/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/en.UTF-8/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/en.utf8/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/en/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    newfstatat(AT_FDCWD, "/dev/zero", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x5), ...}, AT_SYMLINK_NOFOLLOW) = 0
    +++ exited with 0 +++

Here is some basic investigation inside mmdebstrap:

    bash5$ mmdebstrap bullseye /dev/null --customize-hook='chroot $1 bash; false' --include=strace,findutils
    I: automatically chosen mode: unshare
    I: chroot architecture amd64 is equal to the host's architecture
    I: automatically chosen format: tar
    I: using /tmp/mmdebstrap.PqOlBcBbIw as tempdir
    I: running apt-get update...
    done
    I: downloading packages with apt...
    done
    I: extracting archives...
    done
    I: installing essential packages...
    done
    I: downloading apt...
    done
    I: installing apt...
    done
    I: installing remaining packages inside the chroot...
    done
    done
    I: running --customize-hook in shell: sh -c 'chroot $1 bash; false' exec /tmp/mmdebstrap.PqOlBcBbIw

    root@hera:/# find /dev/ -type f
    /dev/zero
    /dev/urandom
    /dev/tty
    /dev/random
    /dev/ptmx
    /dev/null
    /dev/full
    /dev/console

    root@hera:/# strace -e trace=file find /dev/zero -type f -print -quit
    execve("/usr/bin/find", ["find", "/dev/zero", "-type", "f", "-print", "-quit"], 0x7fff5a33ba68 /* 79 vars */) = 0
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/libpcre2-8.so.0", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
    statfs("/sys/fs/selinux", 0x7ffe4aac8df0) = -1 ENOENT (No such file or directory)
    statfs("/selinux", 0x7ffe4aac8df0)      = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/proc/filesystems", O_RDONLY|O_CLOEXEC) = 3
    access("/etc/selinux/config", F_OK)     = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, ".", O_RDONLY|O_CLOEXEC) = 3
    openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MEASUREMENT", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_TELEPHONE", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_ADDRESS", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_NAME", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_PAPER", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MESSAGES/SYS_LC_MESSAGES", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_MONETARY", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_COLLATE", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_TIME", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_NUMERIC", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 4
    openat(AT_FDCWD, "/usr/share/locale/C.UTF-8/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/C.utf8/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    openat(AT_FDCWD, "/usr/share/locale/C/LC_MESSAGES/findutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
    newfstatat(AT_FDCWD, "/dev/zero", {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x5), ...}, AT_SYMLINK_NOFOLLOW) = 0
    +++ exited with 0 +++

    root@hera:/# stat /dev/zero
      File: /dev/zero
      Size: 0         	Blocks: 0          IO Block: 4096   character special file
    Device: 5h/5d	Inode: 6           Links: 1     Device type: 1,5
    Access: (0666/crw-rw-rw-)  Uid: (65534/  nobody)   Gid: (65534/ nogroup)
    Access: 2022-03-25 00:22:02.702830832 +0000
    Modify: 2022-03-25 00:22:02.702830832 +0000
    Change: 2022-03-25 00:22:02.702830832 +0000
     Birth: -

    root@hera:/# if builtin test -f /dev/zero; then echo IS REGULAR; else echo NOT REGULAR; fi
    NOT REGULAR

    root@hera:/# apt install python3 -qq
    ⋮

    root@hera:/# python3 -c 'import pathlib; print(pathlib.Path("/dev/zero").stat())'
    os.stat_result(st_mode=8630, st_ino=6, st_dev=5, st_nlink=1, st_uid=65534, st_gid=65534, st_size=0, st_atime=1648167722, st_mtime=1648167722, st_ctime=1648167722)

    root@hera:/# python3 -c 'import pathlib; print(pathlib.Path("/dev/zero").is_file())'
    False

Oh also... SOME character devices are correctly detected.

    bash$ mmdebstrap --aptopt='Acquire::http::Proxy "http://localhost:3142"' --dpkgopt=force-unsafe-io --variant=apt bullseye /dev/null --customize-hook='chroot $1 bash; false' --include=strace,findutils
    ⋮

    root@hera:/# find /dev -type b -ls

    root@hera:/# find /dev -type c -ls
           14      0 crw--w----   1 nobody   nogroup  136,  11 Mar 25 04:10 /dev/pts/11
           13      0 crw--w----   1 nobody   nogroup  136,  10 Mar 25 04:05 /dev/pts/10
           12      0 crw--w----   1 nobody   nogroup  136,   9 Mar 25 04:11 /dev/pts/9
           10      0 crw--w----   1 nobody   nogroup  136,   7 Mar 25 04:12 /dev/pts/7
            9      0 crw--w----   1 nobody   nogroup  136,   6 Mar 25 04:12 /dev/pts/6
            6      0 crw--w----   1 nobody   nogroup  136,   3 Mar 25 03:44 /dev/pts/3
            8      0 crw--w----   1 nobody   nogroup  136,   5 Mar 25 03:38 /dev/pts/5
            7      0 crw--w----   1 nobody   nogroup  136,   4 Mar 25 04:12 /dev/pts/4
            5      0 crw--w----   1 nobody   nogroup  136,   2 Mar 25 03:42 /dev/pts/2
            4      0 crw--w----   1 nobody   nogroup  136,   1 Mar 25 04:12 /dev/pts/1
            3      0 crw-------   1 nobody   nogroup  136,   0 Mar 25 04:12 /dev/pts/0
            2      0 c---------   1 nobody   nogroup    5,   2 Mar 25 00:21 /dev/pts/ptmx

    root@hera:/# find /dev -type f -ls
            6      0 crw-rw-rw-   1 nobody   nogroup    1,   5 Mar 25 00:22 /dev/zero
            9      0 crw-rw-rw-   1 nobody   nogroup    1,   9 Mar 25 00:22 /dev/urandom
           11      0 crw-rw-rw-   1 nobody   nogroup    5,   0 Mar 25 04:11 /dev/tty
            8      0 crw-rw-rw-   1 nobody   nogroup    1,   8 Mar 25 00:22 /dev/random
           86      0 crw-rw-rw-   1 nobody   nogroup    5,   2 Mar 25 04:12 /dev/ptmx
            4      0 crw-rw-rw-   1 nobody   nogroup    1,   3 Mar 25 00:22 /dev/null
            7      0 crw-rw-rw-   1 nobody   nogroup    1,   7 Mar 25 00:22 /dev/full
           12      0 crw-------   1 nobody   nogroup    5,   1 Mar 25 00:22 /dev/console

#1008240#8
Date:
2022-03-25 06:15:30 UTC
From:
To:
Hi,

Quoting Trent W. Buck (2022-03-25 05:15:41)

the unshared user doesn't have permissions to run mknod but we still need
devices like /dev/null in unshare mode. To solve this problem mmdebstrap
bind-mounts /dev/null to a real file. You can reproduce your findings without
mmdebstrap like so:

sudo touch null
sudo mount -t bind /dev/null null

Now run your find and stat calls and you will get the same results as you did
inside mmdebstrap in unshare mode. So this behaviour is not unique to the
unshared user namespace but happens outside of it as well if you bind-mount
device nodes on files.

Thanks!

cheers, josch