diff mbox series

[v2] linux: Split tst-ttyname

Message ID 20230605183243.3493759-1-adhemerval.zanella@linaro.org
State New
Headers show
Series [v2] linux: Split tst-ttyname | expand

Commit Message

Adhemerval Zanella Netto June 5, 2023, 6:32 p.m. UTC
The tst-ttyname may fail in container environments while trying to
mount the procfs after the unshare (test do_in_chroot_2).  It is not
clear why exaclty kernel returns EPERM in this case, the container
does have CAP_SYS_CHROOT, SELinux/AppArmor is disabled, and there is
no seccomp filter.

To avoid always reporting the test as FAIL in such scenario, the
test that uses new namespaces is moved to a new one and the failure
on the mount command is now report as UNSUPPORTED.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.
---
 sysdeps/unix/sysv/linux/Makefile              |   3 +-
 .../{tst-ttyname.c => tst-ttyname-common.c}   | 208 +-----------------
 sysdeps/unix/sysv/linux/tst-ttyname-direct.c  | 110 +++++++++
 .../unix/sysv/linux/tst-ttyname-namespace.c   | 147 +++++++++++++
 4 files changed, 263 insertions(+), 205 deletions(-)
 rename sysdeps/unix/sysv/linux/{tst-ttyname.c => tst-ttyname-common.c} (68%)
 create mode 100644 sysdeps/unix/sysv/linux/tst-ttyname-direct.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-ttyname-namespace.c

Comments

Florian Weimer June 7, 2023, 9:24 p.m. UTC | #1
* Adhemerval Zanella:

> The tst-ttyname may fail in container environments while trying to
> mount the procfs after the unshare (test do_in_chroot_2).  It is not
> clear why exaclty kernel returns EPERM in this case, the container
> does have CAP_SYS_CHROOT, SELinux/AppArmor is disabled, and there is
> no seccomp filter.
>
> To avoid always reporting the test as FAIL in such scenario, the
> test that uses new namespaces is moved to a new one and the failure
> on the mount command is now report as UNSUPPORTED.

Sorry, there's this change a well:

-          VERIFY (mount ("proc", "/proc", "proc",
-                         MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
+	  if (mount ("proc", "/proc", "proc",
+		     MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
+	    {
+	      /* This happens if we're trying to create a nested container,
+		 like if the build is running under podman, and we lack
+		 priviledges.  */
+	      if (errno == EPERM)
+		_exit (EXIT_UNSUPPORTED);
+	      else
+		_exit (EXIT_FAILURE);
+	    }

I wouldn't expect this based on the commit message.

The systemd developers say that mounting /proc before the
unshare (CLONE_NEWNS | CLONE_NEWPID) call should avoid the failure.

I'd prefer to fix that in a separate change.

Thanks,
Florian
DJ Delorie June 7, 2023, 11:28 p.m. UTC | #2
Florian Weimer via Libc-alpha <libc-alpha@sourceware.org> writes:
> The systemd developers say that mounting /proc before the
> unshare (CLONE_NEWNS | CLONE_NEWPID) call should avoid the failure.

Beware doing this - /proc's contents depend on which pid namespace it's
in, so that things like /proc/self and /proc/$pid work correctly.  If
you mount /proc and then change the pid namespace, /proc will be wrong.
Dmitry V. Levin June 7, 2023, 11:56 p.m. UTC | #3
On Mon, Jun 05, 2023 at 03:32:43PM -0300, Adhemerval Zanella via Libc-alpha wrote:
> The tst-ttyname may fail in container environments while trying to
> mount the procfs after the unshare (test do_in_chroot_2).  It is not
> clear why exaclty kernel returns EPERM in this case, the container
> does have CAP_SYS_CHROOT, SELinux/AppArmor is disabled, and there is
> no seccomp filter.

If it's not yet clear why exactly EPERM is returned in this case,
please re-read https://bugzilla.redhat.com/show_bug.cgi?id=2210335#c10
mentioned earlier in this thread.
Adhemerval Zanella Netto June 8, 2023, 11:53 a.m. UTC | #4
On 07/06/23 18:24, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>> The tst-ttyname may fail in container environments while trying to
>> mount the procfs after the unshare (test do_in_chroot_2).  It is not
>> clear why exaclty kernel returns EPERM in this case, the container
>> does have CAP_SYS_CHROOT, SELinux/AppArmor is disabled, and there is
>> no seccomp filter.
>>
>> To avoid always reporting the test as FAIL in such scenario, the
>> test that uses new namespaces is moved to a new one and the failure
>> on the mount command is now report as UNSUPPORTED.
> 
> Sorry, there's this change a well:
> 
> -          VERIFY (mount ("proc", "/proc", "proc",
> -                         MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
> +	  if (mount ("proc", "/proc", "proc",
> +		     MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
> +	    {
> +	      /* This happens if we're trying to create a nested container,
> +		 like if the build is running under podman, and we lack
> +		 priviledges.  */
> +	      if (errno == EPERM)
> +		_exit (EXIT_UNSUPPORTED);
> +	      else
> +		_exit (EXIT_FAILURE);
> +	    }
> 
> I wouldn't expect this based on the commit message.
> 
> The systemd developers say that mounting /proc before the
> unshare (CLONE_NEWNS | CLONE_NEWPID) call should avoid the failure.
> 
> I'd prefer to fix that in a separate change.

Already, I can add this change in a subsequent patch.


> If it's not yet clear why exactly EPERM is returned in this case,
> please re-read https://bugzilla.redhat.com/show_bug.cgi?id=2210335#c10
> mentioned earlier in this thread.

I have read the thread, but what is not clear to me is how to proper fix
on tst-ttyname.  Running on my environment I do see that after 
support_become_root the process mount point is indeed 'polluted' with
extra procfs mount added by the container itself:

/proc/self/mountinfo: 1241 1240 0:184 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
/proc/self/mountinfo: 1242 1241 0:184 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
/proc/self/mountinfo: 1243 1241 0:184 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw
/proc/self/mountinfo: 1244 1241 0:184 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
/proc/self/mountinfo: 1249 1241 0:184 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
/proc/self/mountinfo: 1257 1241 0:184 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
/proc/self/mountinfo: 1258 1241 0:189 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64
/proc/self/mountinfo: 1259 1241 0:185 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
/proc/self/mountinfo: 1260 1241 0:185 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
/proc/self/mountinfo: 1375 1241 0:185 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
/proc/self/mountinfo: 1410 1241 0:190 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64

The 'fix' that Christian has suggested is out of the scope of the test
because afaiu it should be done by the container management (before
spawning the process that trigger the glibc testcase).

I have tried to move this test to test-container, but it circles back
to the same issue.  That's why I have added the UNSUPPORTED if the inner
mount fails (I think I can improve the commit message).
Christian Brauner June 9, 2023, 7:49 a.m. UTC | #5
On Thu, Jun 08, 2023 at 08:53:42AM -0300, Adhemerval Zanella Netto via Libc-alpha wrote:
> 
> 
> On 07/06/23 18:24, Florian Weimer wrote:
> > * Adhemerval Zanella:
> > 
> >> The tst-ttyname may fail in container environments while trying to
> >> mount the procfs after the unshare (test do_in_chroot_2).  It is not
> >> clear why exaclty kernel returns EPERM in this case, the container
> >> does have CAP_SYS_CHROOT, SELinux/AppArmor is disabled, and there is
> >> no seccomp filter.
> >>
> >> To avoid always reporting the test as FAIL in such scenario, the
> >> test that uses new namespaces is moved to a new one and the failure
> >> on the mount command is now report as UNSUPPORTED.
> > 
> > Sorry, there's this change a well:
> > 
> > -          VERIFY (mount ("proc", "/proc", "proc",
> > -                         MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
> > +	  if (mount ("proc", "/proc", "proc",
> > +		     MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
> > +	    {
> > +	      /* This happens if we're trying to create a nested container,
> > +		 like if the build is running under podman, and we lack
> > +		 priviledges.  */
> > +	      if (errno == EPERM)
> > +		_exit (EXIT_UNSUPPORTED);
> > +	      else
> > +		_exit (EXIT_FAILURE);
> > +	    }
> > 
> > I wouldn't expect this based on the commit message.
> > 
> > The systemd developers say that mounting /proc before the
> > unshare (CLONE_NEWNS | CLONE_NEWPID) call should avoid the failure.
> > 
> > I'd prefer to fix that in a separate change.
> 
> Already, I can add this change in a subsequent patch.
> 
> 
> > If it's not yet clear why exactly EPERM is returned in this case,
> > please re-read https://bugzilla.redhat.com/show_bug.cgi?id=2210335#c10
> > mentioned earlier in this thread.
> 
> I have read the thread, but what is not clear to me is how to proper fix
> on tst-ttyname.  Running on my environment I do see that after 
> support_become_root the process mount point is indeed 'polluted' with
> extra procfs mount added by the container itself:
> 
> /proc/self/mountinfo: 1241 1240 0:184 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
> /proc/self/mountinfo: 1242 1241 0:184 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
> /proc/self/mountinfo: 1243 1241 0:184 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw
> /proc/self/mountinfo: 1244 1241 0:184 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
> /proc/self/mountinfo: 1249 1241 0:184 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
> /proc/self/mountinfo: 1257 1241 0:184 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
> /proc/self/mountinfo: 1258 1241 0:189 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64
> /proc/self/mountinfo: 1259 1241 0:185 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
> /proc/self/mountinfo: 1260 1241 0:185 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
> /proc/self/mountinfo: 1375 1241 0:185 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
> /proc/self/mountinfo: 1410 1241 0:190 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64
> 
> The 'fix' that Christian has suggested is out of the scope of the test
> because afaiu it should be done by the container management (before
> spawning the process that trigger the glibc testcase).

So there's a few things to consider. What I wrote in that bug report
applies to unprivileged containers only - which is what the
systemd-nspawn bug report was about. If you're using a privileged
container things are different. So you need to figure that out first.

(1) unprivileged container
    If this is an unprivileged container then you can test whether my
    fix works by trying:

    umount /proc/kcore

    If that works then the mount isn't locked. So that means you could
    reveal the underlying files. If that's the case then within the
    container manager you can do:

    mount -t proc proc /run/host/proc

(2) privileged container
     (i) If this is a privileged container your container manager might
	 simply have dropped CAP_SYS_ADMIN so you can neither mount or
	 umount.
    (ii) The files and directories covering various proc files and
         directories are protected by an LSM profile making it
	 impossible to unmount them. For this to be meaningful you would
	 need to be restricted either from creating namespaces - which
	 doesn't seem to be the case - or the LSM profile would have to
	 restrict you from mounting procfs.

         If you happen to have bpftrace you can test this quickly by:

	 # This will report the return value of security_sb_mount().
	 sudo bpftrace -e 'kretprobe:security_sb_mount { printf("returned: %d\n", retval); }'

	 and then try:

	 mount -t proc proc /mnt

	 and see whether you get an errno.
Adhemerval Zanella Netto June 9, 2023, 3:08 p.m. UTC | #6
On 09/06/23 04:49, Christian Brauner wrote:
> On Thu, Jun 08, 2023 at 08:53:42AM -0300, Adhemerval Zanella Netto via Libc-alpha wrote:
>>
>>
>> On 07/06/23 18:24, Florian Weimer wrote:
>>> * Adhemerval Zanella:
>>>
>>>> The tst-ttyname may fail in container environments while trying to
>>>> mount the procfs after the unshare (test do_in_chroot_2).  It is not
>>>> clear why exaclty kernel returns EPERM in this case, the container
>>>> does have CAP_SYS_CHROOT, SELinux/AppArmor is disabled, and there is
>>>> no seccomp filter.
>>>>
>>>> To avoid always reporting the test as FAIL in such scenario, the
>>>> test that uses new namespaces is moved to a new one and the failure
>>>> on the mount command is now report as UNSUPPORTED.
>>>
>>> Sorry, there's this change a well:
>>>
>>> -          VERIFY (mount ("proc", "/proc", "proc",
>>> -                         MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
>>> +	  if (mount ("proc", "/proc", "proc",
>>> +		     MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
>>> +	    {
>>> +	      /* This happens if we're trying to create a nested container,
>>> +		 like if the build is running under podman, and we lack
>>> +		 priviledges.  */
>>> +	      if (errno == EPERM)
>>> +		_exit (EXIT_UNSUPPORTED);
>>> +	      else
>>> +		_exit (EXIT_FAILURE);
>>> +	    }
>>>
>>> I wouldn't expect this based on the commit message.
>>>
>>> The systemd developers say that mounting /proc before the
>>> unshare (CLONE_NEWNS | CLONE_NEWPID) call should avoid the failure.
>>>
>>> I'd prefer to fix that in a separate change.
>>
>> Already, I can add this change in a subsequent patch.
>>
>>
>>> If it's not yet clear why exactly EPERM is returned in this case,
>>> please re-read https://bugzilla.redhat.com/show_bug.cgi?id=2210335#c10
>>> mentioned earlier in this thread.
>>
>> I have read the thread, but what is not clear to me is how to proper fix
>> on tst-ttyname.  Running on my environment I do see that after 
>> support_become_root the process mount point is indeed 'polluted' with
>> extra procfs mount added by the container itself:
>>
>> /proc/self/mountinfo: 1241 1240 0:184 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
>> /proc/self/mountinfo: 1242 1241 0:184 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw
>> /proc/self/mountinfo: 1243 1241 0:184 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw
>> /proc/self/mountinfo: 1244 1241 0:184 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw
>> /proc/self/mountinfo: 1249 1241 0:184 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw
>> /proc/self/mountinfo: 1257 1241 0:184 /sysrq-trigger /proc/sysrq-trigger ro,nosuid,nodev,noexec,relatime - proc proc rw
>> /proc/self/mountinfo: 1258 1241 0:189 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64
>> /proc/self/mountinfo: 1259 1241 0:185 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
>> /proc/self/mountinfo: 1260 1241 0:185 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
>> /proc/self/mountinfo: 1375 1241 0:185 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64
>> /proc/self/mountinfo: 1410 1241 0:190 / /proc/scsi ro,relatime - tmpfs tmpfs ro,inode64
>>
>> The 'fix' that Christian has suggested is out of the scope of the test
>> because afaiu it should be done by the container management (before
>> spawning the process that trigger the glibc testcase).
> 
> So there's a few things to consider. What I wrote in that bug report
> applies to unprivileged containers only - which is what the
> systemd-nspawn bug report was about. If you're using a privileged
> container things are different. So you need to figure that out first.
> 
> (1) unprivileged container
>     If this is an unprivileged container then you can test whether my
>     fix works by trying:
> 
>     umount /proc/kcore
> 
>     If that works then the mount isn't locked. So that means you could
>     reveal the underlying files. If that's the case then within the
>     container manager you can do:
> 
>     mount -t proc proc /run/host/proc

Thanks for the explanation, but I still not really sure what this test
still in my environment (basically a docker container with CAP_SYS_ADMIN
plus apparmor disabled).

It does not seem to be this case, the running within the container and
adding a explicit umount on the test:

$ strace -e umount2 -f misc/tst-ttyname-namespace --direct
umount2("/proc/kcore", 0)               = -1 EINVAL (Invalid argument)

> 
> (2) privileged container
>      (i) If this is a privileged container your container manager might
> 	 simply have dropped CAP_SYS_ADMIN so you can neither mount or
> 	 umount.
>     (ii) The files and directories covering various proc files and
>          directories are protected by an LSM profile making it
> 	 impossible to unmount them. For this to be meaningful you would
> 	 need to be restricted either from creating namespaces - which
> 	 doesn't seem to be the case - or the LSM profile would have to
> 	 restrict you from mounting procfs.
> 
>          If you happen to have bpftrace you can test this quickly by:
> 
> 	 # This will report the return value of security_sb_mount().
> 	 sudo bpftrace -e 'kretprobe:security_sb_mount { printf("returned: %d\n", retval); }'
> 
> 	 and then try:
> 
> 	 mount -t proc proc /mnt
> 
> 	 and see whether you get an errno.

I am not really seeing any issue here:

# bpftrace -e 'kretprobe:security_sb_mount { printf("returned: %d\n", retval); }'
Attaching 1 probe...
returned: 0
returned: 0
returned: 0
returned: 0
returned: 0

While running:

$ misc/tst-ttyname-namespace
info:  entering chroot 2
info:    testcase: basic smoketest
info:      ttyname: PASS {name="/dev/pts/2", errno=0}
info:      ttyname_r: PASS {name="/dev/pts/2", ret=0, errno=0}
error: ../sysdeps/unix/sysv/linux/tst-ttyname-namespace.c:97: mount ("proc", "/proc", "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0: Operation not permitted

This is being really time consuming to figure out the underlying issue, so
I really think we should move the test to make it as UNSUPPORTED if the procfs
mount fails; it really does not seem to be a test failure but rather system 
configuration issue (which fails on the UNSUPPORTED case).
diff mbox series

Patch

diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 594a5dc53e..23a84cf225 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -224,7 +224,8 @@  tests += \
   tst-sysvshm-linux \
   tst-tgkill \
   tst-timerfd \
-  tst-ttyname \
+  tst-ttyname-direct \
+  tst-ttyname-namespace \
   # tests
 
 # process_madvise requires CAP_SYS_ADMIN.
diff --git a/sysdeps/unix/sysv/linux/tst-ttyname.c b/sysdeps/unix/sysv/linux/tst-ttyname-common.c
similarity index 68%
rename from sysdeps/unix/sysv/linux/tst-ttyname.c
rename to sysdeps/unix/sysv/linux/tst-ttyname-common.c
index ef55665fbe..3f6d8ee944 100644
--- a/sysdeps/unix/sysv/linux/tst-ttyname.c
+++ b/sysdeps/unix/sysv/linux/tst-ttyname-common.c
@@ -1,4 +1,5 @@ 
-/* Copyright (C) 2017-2023 Free Software Foundation, Inc.
+/* Common definitions for ttyname tests.
+   Copyright (C) 2017-2023 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -19,19 +20,16 @@ 
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
-#include <sched.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/mount.h>
-#include <sys/prctl.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
 #include <sys/resource.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
 #include <support/check.h>
-#include <support/namespace.h>
 #include <support/support.h>
 #include <support/temp_file.h>
 #include <support/test-driver.h>
@@ -266,187 +264,6 @@  adjust_file_limit (const char *pty)
     }
 }
 
-/* These chroot setup functions put the TTY at at "/console" (where it
-   won't be found by ttyname), and create "/dev/console" as an
-   ordinary file.  This way, it's easier to write test-cases that
-   expect ttyname to fail; test-cases that expect it to succeed need
-   to explicitly remount it at "/dev/console".  */
-
-static int
-do_in_chroot_1 (int (*cb)(const char *, int))
-{
-  printf ("info:  entering chroot 1\n");
-
-  /* Open the PTS that we'll be testing on.  */
-  int master;
-  char *slavename;
-  master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
-  if (master < 0)
-    {
-      if (errno == ENOENT)
-	FAIL_UNSUPPORTED ("posix_openpt: %m");
-      else
-	FAIL_EXIT1 ("posix_openpt: %m");
-    }
-  VERIFY ((slavename = ptsname (master)));
-  VERIFY (unlockpt (master) == 0);
-  if (strncmp (slavename, "/dev/pts/", 9) != 0)
-    FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
-                      slavename);
-  adjust_file_limit (slavename);
-  int slave = xopen (slavename, O_RDWR, 0);
-  if (!doit (slave, "basic smoketest",
-             (struct result_r){.name=slavename, .ret=0, .err=0}))
-    return 1;
-
-  pid_t pid = xfork ();
-  if (pid == 0)
-    {
-      xclose (master);
-
-      if (!support_enter_mount_namespace ())
-	FAIL_UNSUPPORTED ("could not enter new mount namespace");
-
-      VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
-      VERIFY (chdir (chrootdir) == 0);
-
-      xmkdir ("proc", 0755);
-      xmkdir ("dev", 0755);
-      xmkdir ("dev/pts", 0755);
-
-      VERIFY (mount ("/proc", "proc", NULL, MS_BIND|MS_REC, NULL) == 0);
-      VERIFY (mount ("devpts", "dev/pts", "devpts",
-                     MS_NOSUID|MS_NOEXEC,
-                     "newinstance,ptmxmode=0666,mode=620") == 0);
-      VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
-
-      touch ("console", 0);
-      touch ("dev/console", 0);
-      VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
-
-      xchroot (".");
-
-      char *linkname = xasprintf ("/proc/self/fd/%d", slave);
-      char *target = proc_fd_readlink (linkname);
-      VERIFY (strcmp (target, slavename) == 0);
-      free (linkname);
-
-      _exit (cb (slavename, slave));
-    }
-  int status;
-  xwaitpid (pid, &status, 0);
-  VERIFY (WIFEXITED (status));
-  xclose (master);
-  xclose (slave);
-  return WEXITSTATUS (status);
-}
-
-static int
-do_in_chroot_2 (int (*cb)(const char *, int))
-{
-  printf ("info:  entering chroot 2\n");
-
-  int pid_pipe[2];
-  xpipe (pid_pipe);
-  int exit_pipe[2];
-  xpipe (exit_pipe);
-
-  /* Open the PTS that we'll be testing on.  */
-  int master;
-  char *slavename;
-  VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
-  VERIFY ((slavename = ptsname (master)));
-  VERIFY (unlockpt (master) == 0);
-  if (strncmp (slavename, "/dev/pts/", 9) != 0)
-    FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
-                      slavename);
-  adjust_file_limit (slavename);
-  /* wait until in a new mount ns to open the slave */
-
-  /* enable `wait`ing on grandchildren */
-  VERIFY (prctl (PR_SET_CHILD_SUBREAPER, 1) == 0);
-
-  pid_t pid = xfork (); /* outer child */
-  if (pid == 0)
-    {
-      xclose (master);
-      xclose (pid_pipe[0]);
-      xclose (exit_pipe[1]);
-
-      if (!support_enter_mount_namespace ())
-	FAIL_UNSUPPORTED ("could not enter new mount namespace");
-
-      int slave = xopen (slavename, O_RDWR, 0);
-      if (!doit (slave, "basic smoketest",
-                 (struct result_r){.name=slavename, .ret=0, .err=0}))
-        _exit (1);
-
-      VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
-      VERIFY (chdir (chrootdir) == 0);
-
-      xmkdir ("proc", 0755);
-      xmkdir ("dev", 0755);
-      xmkdir ("dev/pts", 0755);
-
-      VERIFY (mount ("devpts", "dev/pts", "devpts",
-                     MS_NOSUID|MS_NOEXEC,
-                     "newinstance,ptmxmode=0666,mode=620") == 0);
-      VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
-
-      touch ("console", 0);
-      touch ("dev/console", 0);
-      VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
-
-      xchroot (".");
-
-      if (unshare (CLONE_NEWNS | CLONE_NEWPID) < 0)
-        FAIL_UNSUPPORTED ("could not enter new PID namespace");
-      pid = xfork (); /* inner child */
-      if (pid == 0)
-        {
-          xclose (pid_pipe[1]);
-
-          /* wait until the outer child has exited */
-          char c;
-          VERIFY (read (exit_pipe[0], &c, 1) == 0);
-          xclose (exit_pipe[0]);
-
-          VERIFY (mount ("proc", "/proc", "proc",
-                         MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
-
-          char *linkname = xasprintf ("/proc/self/fd/%d", slave);
-          char *target = proc_fd_readlink (linkname);
-          VERIFY (strcmp (target, strrchr (slavename, '/')) == 0);
-          free (linkname);
-
-          _exit (cb (slavename, slave));
-        }
-      xwrite (pid_pipe[1], &pid, sizeof pid);
-      _exit (0);
-    }
-  xclose (pid_pipe[1]);
-  xclose (exit_pipe[0]);
-  xclose (exit_pipe[1]);
-
-  /* wait for the outer child */
-  int status;
-  xwaitpid (pid, &status, 0);
-  VERIFY (WIFEXITED (status));
-  int ret = WEXITSTATUS (status);
-  if (ret != 0)
-    return ret;
-
-  /* set 'pid' to the inner child */
-  VERIFY (read (pid_pipe[0], &pid, sizeof pid) == sizeof pid);
-  xclose (pid_pipe[0]);
-
-  /* wait for the inner child */
-  xwaitpid (pid, &status, 0);
-  VERIFY (WIFEXITED (status));
-  xclose (master);
-  return WEXITSTATUS (status);
-}
-
 /* main test */
 
 static int
@@ -597,20 +414,3 @@  run_chroot_tests (const char *slavename, int slave)
   return ok ? 0 : 1;
 }
 
-static int
-do_test (void)
-{
-  support_become_root ();
-
-  int ret1 = do_in_chroot_1 (run_chroot_tests);
-  if (ret1 == EXIT_UNSUPPORTED)
-    return ret1;
-
-  int ret2 = do_in_chroot_2 (run_chroot_tests);
-  if (ret2 == EXIT_UNSUPPORTED)
-    return ret2;
-
-  return  ret1 | ret2;
-}
-
-#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/tst-ttyname-direct.c b/sysdeps/unix/sysv/linux/tst-ttyname-direct.c
new file mode 100644
index 0000000000..04601777d6
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-ttyname-direct.c
@@ -0,0 +1,110 @@ 
+/* Copyright (C) 2017-2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, see <https://www.gnu.org/licenses/>.  */
+
+#include <sched.h>
+#include <sys/prctl.h>
+
+#include <support/namespace.h>
+
+#include "tst-ttyname-common.c"
+
+/* These chroot setup functions put the TTY at at "/console" (where it
+   won't be found by ttyname), and create "/dev/console" as an
+   ordinary file.  This way, it's easier to write test-cases that
+   expect ttyname to fail; test-cases that expect it to succeed need
+   to explicitly remount it at "/dev/console".  */
+
+static int
+do_in_chroot_1 (int (*cb)(const char *, int))
+{
+  printf ("info:  entering chroot 1\n");
+
+  /* Open the PTS that we'll be testing on.  */
+  int master;
+  char *slavename;
+  master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
+  if (master < 0)
+    {
+      if (errno == ENOENT)
+	FAIL_UNSUPPORTED ("posix_openpt: %m");
+      else
+	FAIL_EXIT1 ("posix_openpt: %m");
+    }
+  VERIFY ((slavename = ptsname (master)));
+  VERIFY (unlockpt (master) == 0);
+  if (strncmp (slavename, "/dev/pts/", 9) != 0)
+    FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
+                      slavename);
+  adjust_file_limit (slavename);
+  int slave = xopen (slavename, O_RDWR, 0);
+  if (!doit (slave, "basic smoketest",
+             (struct result_r){.name=slavename, .ret=0, .err=0}))
+    return 1;
+
+  pid_t pid = xfork ();
+  if (pid == 0)
+    {
+      xclose (master);
+
+      if (!support_enter_mount_namespace ())
+	FAIL_UNSUPPORTED ("could not enter new mount namespace");
+
+      VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
+      VERIFY (chdir (chrootdir) == 0);
+
+      xmkdir ("proc", 0755);
+      xmkdir ("dev", 0755);
+      xmkdir ("dev/pts", 0755);
+
+      VERIFY (mount ("/proc", "proc", NULL, MS_BIND|MS_REC, NULL) == 0);
+      VERIFY (mount ("devpts", "dev/pts", "devpts",
+                     MS_NOSUID|MS_NOEXEC,
+                     "newinstance,ptmxmode=0666,mode=620") == 0);
+      VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
+
+      touch ("console", 0);
+      touch ("dev/console", 0);
+      VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
+
+      xchroot (".");
+
+      char *linkname = xasprintf ("/proc/self/fd/%d", slave);
+      char *target = proc_fd_readlink (linkname);
+      VERIFY (strcmp (target, slavename) == 0);
+      free (linkname);
+
+      _exit (cb (slavename, slave));
+    }
+  int status;
+  xwaitpid (pid, &status, 0);
+  VERIFY (WIFEXITED (status));
+  xclose (master);
+  xclose (slave);
+  return WEXITSTATUS (status);
+}
+
+static int
+do_test (void)
+{
+  support_become_root ();
+
+  do_in_chroot_1 (run_chroot_tests);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/tst-ttyname-namespace.c b/sysdeps/unix/sysv/linux/tst-ttyname-namespace.c
new file mode 100644
index 0000000000..7b74258c10
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-ttyname-namespace.c
@@ -0,0 +1,147 @@ 
+/* Tests for ttyname/ttyname_r with namespaces.
+   Copyright (C) 2017-2023 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public License as
+   published by the Free Software Foundation; either version 2.1 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, see <https://www.gnu.org/licenses/>.  */
+
+#include <sched.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#include <support/namespace.h>
+
+#include "tst-ttyname-common.c"
+
+static int
+do_in_chroot_2 (int (*cb)(const char *, int))
+{
+  printf ("info:  entering chroot 2\n");
+
+  int pid_pipe[2];
+  xpipe (pid_pipe);
+  int exit_pipe[2];
+  xpipe (exit_pipe);
+
+  /* Open the PTS that we'll be testing on.  */
+  int master;
+  char *slavename;
+  VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
+  VERIFY ((slavename = ptsname (master)));
+  VERIFY (unlockpt (master) == 0);
+  if (strncmp (slavename, "/dev/pts/", 9) != 0)
+    FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
+                      slavename);
+  adjust_file_limit (slavename);
+  /* wait until in a new mount ns to open the slave */
+
+  /* enable `wait`ing on grandchildren */
+  VERIFY (prctl (PR_SET_CHILD_SUBREAPER, 1) == 0);
+
+  pid_t pid = xfork (); /* outer child */
+  if (pid == 0)
+    {
+      xclose (master);
+      xclose (pid_pipe[0]);
+      xclose (exit_pipe[1]);
+
+      if (!support_enter_mount_namespace ())
+	FAIL_UNSUPPORTED ("could not enter new mount namespace");
+
+      int slave = xopen (slavename, O_RDWR, 0);
+      if (!doit (slave, "basic smoketest",
+                 (struct result_r){.name=slavename, .ret=0, .err=0}))
+        _exit (1);
+
+      VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
+      VERIFY (chdir (chrootdir) == 0);
+
+      xmkdir ("proc", 0755);
+      xmkdir ("dev", 0755);
+      xmkdir ("dev/pts", 0755);
+
+      VERIFY (mount ("devpts", "dev/pts", "devpts",
+                     MS_NOSUID|MS_NOEXEC,
+                     "newinstance,ptmxmode=0666,mode=620") == 0);
+      VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
+
+      touch ("console", 0);
+      touch ("dev/console", 0);
+      VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
+
+      xchroot (".");
+
+      if (unshare (CLONE_NEWNS | CLONE_NEWPID) < 0)
+        FAIL_UNSUPPORTED ("could not enter new PID namespace");
+      pid = xfork (); /* inner child */
+      if (pid == 0)
+        {
+          xclose (pid_pipe[1]);
+
+          /* wait until the outer child has exited */
+          char c;
+          VERIFY (read (exit_pipe[0], &c, 1) == 0);
+          xclose (exit_pipe[0]);
+
+	  if (mount ("proc", "/proc", "proc",
+		     MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
+	    {
+	      /* This happens if we're trying to create a nested container,
+		 like if the build is running under podman, and we lack
+		 priviledges.  */
+	      if (errno == EPERM)
+		_exit (EXIT_UNSUPPORTED);
+	      else
+		_exit (EXIT_FAILURE);
+	    }
+
+          char *linkname = xasprintf ("/proc/self/fd/%d", slave);
+          char *target = proc_fd_readlink (linkname);
+          VERIFY (strcmp (target, strrchr (slavename, '/')) == 0);
+          free (linkname);
+
+          _exit (cb (slavename, slave));
+        }
+
+      int status;
+      xwaitpid (pid, &status, 0);
+      _exit (WEXITSTATUS (status));
+    }
+  xclose (pid_pipe[1]);
+  xclose (exit_pipe[0]);
+  xclose (exit_pipe[1]);
+
+  /* wait for the outer child */
+  int status;
+  xwaitpid (pid, &status, 0);
+  VERIFY (WIFEXITED (status));
+  int ret = WEXITSTATUS (status);
+  if (ret != 0)
+    FAIL_UNSUPPORTED ("unable to mount /proc on inner child process");
+  xclose (master);
+
+  return 0;
+}
+
+static int
+do_test (void)
+{
+  support_become_root ();
+
+  do_in_chroot_2 (run_chroot_tests);
+
+  return 0;
+}
+
+#include <support/test-driver.c>