1/* Copyright (C) 2017-2022 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3
4 The GNU C Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
8
9 The GNU C Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with the GNU C Library; see the file COPYING.LIB. If
16 not, see <https://www.gnu.org/licenses/>. */
17
18#include <dirent.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <limits.h>
22#include <sched.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/mount.h>
27#include <sys/prctl.h>
28#include <sys/stat.h>
29#include <sys/wait.h>
30#include <sys/resource.h>
31#include <unistd.h>
32
33#include <support/check.h>
34#include <support/namespace.h>
35#include <support/support.h>
36#include <support/temp_file.h>
37#include <support/test-driver.h>
38#include <support/xunistd.h>
39
40/* generic utilities */
41
42#define VERIFY(expr) \
43 do { \
44 if (!(expr)) \
45 { \
46 printf ("error: %s:%d: %s: %m\n", \
47 __FILE__, __LINE__, #expr); \
48 exit (1); \
49 } \
50 } while (0)
51
52static void
53touch (const char *path, mode_t mode)
54{
55 xclose (xopen (path, O_WRONLY|O_CREAT|O_NOCTTY, mode));
56}
57
58static size_t
59trim_prefix (char *str, size_t str_len, const char *prefix)
60{
61 size_t prefix_len = strlen (prefix);
62 if (str_len > prefix_len && memcmp (str, prefix, prefix_len) == 0)
63 {
64 memmove (str, str + prefix_len, str_len - prefix_len);
65 return str_len - prefix_len;
66 }
67 return str_len;
68}
69
70/* returns a pointer to static storage */
71static char *
72proc_fd_readlink (const char *linkname)
73{
74 static char target[PATH_MAX+1];
75 ssize_t target_len = readlink (path: linkname, buf: target, PATH_MAX);
76 VERIFY (target_len > 0);
77 target_len = trim_prefix (str: target, str_len: target_len, prefix: "(unreachable)");
78 target[target_len] = '\0';
79 return target;
80}
81
82/* plain ttyname runner */
83
84struct result
85{
86 const char *name;
87 int err;
88};
89
90/* strings in result structure are in static storage */
91static struct result
92run_ttyname (int fd)
93{
94 struct result ret;
95 errno = 0;
96 ret.name = ttyname (fd: fd);
97 ret.err = errno;
98 return ret;
99}
100
101static bool
102eq_ttyname (struct result actual, struct result expected)
103{
104 char *actual_name, *expected_name;
105
106 if ((actual.err == expected.err)
107 && (!actual.name == !expected.name)
108 && (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
109 {
110 if (expected.name)
111 expected_name = xasprintf (format: "\"%s\"", expected.name);
112 else
113 expected_name = xstrdup ("NULL");
114
115 printf (format: "info: ttyname: PASS {name=%s, errno=%d}\n",
116 expected_name, expected.err);
117
118 free (ptr: expected_name);
119 return true;
120 }
121
122 if (actual.name)
123 actual_name = xasprintf (format: "\"%s\"", actual.name);
124 else
125 actual_name = xstrdup ("NULL");
126
127 if (expected.name)
128 expected_name = xasprintf (format: "\"%s\"", expected.name);
129 else
130 expected_name = xstrdup ("NULL");
131
132 printf (format: "error: ttyname: actual {name=%s, errno=%d} != expected {name=%s, errno=%d}\n",
133 actual_name, actual.err,
134 expected_name, expected.err);
135
136 free (ptr: actual_name);
137 free (ptr: expected_name);
138 return false;
139}
140
141/* ttyname_r runner */
142
143struct result_r
144{
145 const char *name;
146 int ret;
147 int err;
148};
149
150/* strings in result structure are in static storage */
151static struct result_r
152run_ttyname_r (int fd)
153{
154 static char buf[TTY_NAME_MAX];
155
156 struct result_r ret;
157 errno = 0;
158 ret.ret = ttyname_r (fd: fd, buf: buf, TTY_NAME_MAX);
159 ret.err = errno;
160 if (ret.ret == 0)
161 ret.name = buf;
162 else
163 ret.name = NULL;
164 return ret;
165}
166
167static bool
168eq_ttyname_r (struct result_r actual, struct result_r expected)
169{
170 char *actual_name, *expected_name;
171
172 if ((actual.err == expected.err)
173 && (actual.ret == expected.ret)
174 && (!actual.name == !expected.name)
175 && (actual.name ? strcmp (actual.name, expected.name) == 0 : true))
176 {
177 if (expected.name)
178 expected_name = xasprintf (format: "\"%s\"", expected.name);
179 else
180 expected_name = xstrdup ("NULL");
181
182 printf (format: "info: ttyname_r: PASS {name=%s, ret=%d, errno=%d}\n",
183 expected_name, expected.ret, expected.err);
184
185 free (ptr: expected_name);
186 return true;
187 }
188
189 if (actual.name)
190 actual_name = xasprintf (format: "\"%s\"", actual.name);
191 else
192 actual_name = xstrdup ("NULL");
193
194 if (expected.name)
195 expected_name = xasprintf (format: "\"%s\"", expected.name);
196 else
197 expected_name = xstrdup ("NULL");
198
199 printf (format: "error: ttyname_r: actual {name=%s, ret=%d, errno=%d} != expected {name=%s, ret=%d, errno=%d}\n",
200 actual_name, actual.ret, actual.err,
201 expected_name, expected.ret, expected.err);
202
203 free (ptr: actual_name);
204 free (ptr: expected_name);
205 return false;
206}
207
208/* combined runner */
209
210static bool
211doit (int fd, const char *testname, struct result_r expected_r)
212{
213 struct result expected = {.name=expected_r.name, .err=expected_r.ret};
214 bool ret = true;
215
216 printf (format: "info: testcase: %s\n", testname);
217
218 if (!eq_ttyname (actual: run_ttyname (fd), expected))
219 ret = false;
220 if (!eq_ttyname_r (actual: run_ttyname_r (fd), expected: expected_r))
221 ret = false;
222
223 if (!ret)
224 support_record_failure ();
225
226 return ret;
227}
228
229/* chroot setup */
230
231static char *chrootdir;
232
233static void
234prepare (int argc, char **argv)
235{
236 chrootdir = xasprintf (format: "%s/tst-ttyname-XXXXXX", test_dir);
237 if (mkdtemp (template: chrootdir) == NULL)
238 FAIL_EXIT1 ("mkdtemp (\"%s\"): %m", chrootdir);
239 add_temp_file (name: chrootdir);
240}
241#define PREPARE prepare
242
243/* Adjust the file limit so that we have a chance to open PTY. */
244static void
245adjust_file_limit (const char *pty)
246{
247 int number = -1;
248 if (sscanf (pty, "/dev/pts/%d", &number) != 1 || number < 0)
249 FAIL_EXIT1 ("invalid PTY name: \"%s\"", pty);
250
251 /* Add a few additional descriptors to cover standard I/O streams
252 etc. */
253 rlim_t desired_limit = number + 10;
254
255 struct rlimit lim;
256 if (getrlimit (RLIMIT_NOFILE, rlimits: &lim) != 0)
257 FAIL_EXIT1 ("getrlimit (RLIMIT_NOFILE): %m");
258 if (lim.rlim_cur < desired_limit)
259 {
260 printf (format: "info: adjusting RLIMIT_NOFILE from %llu to %llu\n",
261 (unsigned long long int) lim.rlim_cur,
262 (unsigned long long int) desired_limit);
263 lim.rlim_cur = desired_limit;
264 if (setrlimit (RLIMIT_NOFILE, rlimits: &lim) != 0)
265 printf (format: "warning: setrlimit (RLIMIT_NOFILE) failed: %m\n");
266 }
267}
268
269/* These chroot setup functions put the TTY at at "/console" (where it
270 won't be found by ttyname), and create "/dev/console" as an
271 ordinary file. This way, it's easier to write test-cases that
272 expect ttyname to fail; test-cases that expect it to succeed need
273 to explicitly remount it at "/dev/console". */
274
275static int
276do_in_chroot_1 (int (*cb)(const char *, int))
277{
278 printf (format: "info: entering chroot 1\n");
279
280 /* Open the PTS that we'll be testing on. */
281 int master;
282 char *slavename;
283 master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK);
284 if (master < 0)
285 {
286 if (errno == ENOENT)
287 FAIL_UNSUPPORTED ("posix_openpt: %m");
288 else
289 FAIL_EXIT1 ("posix_openpt: %m");
290 }
291 VERIFY ((slavename = ptsname (master)));
292 VERIFY (unlockpt (master) == 0);
293 if (strncmp (slavename, "/dev/pts/", 9) != 0)
294 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
295 slavename);
296 adjust_file_limit (pty: slavename);
297 int slave = xopen (path: slavename, O_RDWR, 0);
298 if (!doit (fd: slave, testname: "basic smoketest",
299 expected_r: (struct result_r){.name=slavename, .ret=0, .err=0}))
300 return 1;
301
302 pid_t pid = xfork ();
303 if (pid == 0)
304 {
305 xclose (master);
306
307 if (!support_enter_mount_namespace ())
308 FAIL_UNSUPPORTED ("could not enter new mount namespace");
309
310 VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
311 VERIFY (chdir (chrootdir) == 0);
312
313 xmkdir (path: "proc", 0755);
314 xmkdir (path: "dev", 0755);
315 xmkdir (path: "dev/pts", 0755);
316
317 VERIFY (mount ("/proc", "proc", NULL, MS_BIND|MS_REC, NULL) == 0);
318 VERIFY (mount ("devpts", "dev/pts", "devpts",
319 MS_NOSUID|MS_NOEXEC,
320 "newinstance,ptmxmode=0666,mode=620") == 0);
321 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
322
323 touch (path: "console", mode: 0);
324 touch (path: "dev/console", mode: 0);
325 VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
326
327 xchroot (path: ".");
328
329 char *linkname = xasprintf (format: "/proc/self/fd/%d", slave);
330 char *target = proc_fd_readlink (linkname);
331 VERIFY (strcmp (target, slavename) == 0);
332 free (ptr: linkname);
333
334 _exit (cb (slavename, slave));
335 }
336 int status;
337 xwaitpid (pid, status: &status, flags: 0);
338 VERIFY (WIFEXITED (status));
339 xclose (master);
340 xclose (slave);
341 return WEXITSTATUS (status);
342}
343
344static int
345do_in_chroot_2 (int (*cb)(const char *, int))
346{
347 printf (format: "info: entering chroot 2\n");
348
349 int pid_pipe[2];
350 xpipe (pid_pipe);
351 int exit_pipe[2];
352 xpipe (exit_pipe);
353
354 /* Open the PTS that we'll be testing on. */
355 int master;
356 char *slavename;
357 VERIFY ((master = posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK)) >= 0);
358 VERIFY ((slavename = ptsname (master)));
359 VERIFY (unlockpt (master) == 0);
360 if (strncmp (slavename, "/dev/pts/", 9) != 0)
361 FAIL_UNSUPPORTED ("slave pseudo-terminal is not under /dev/pts/: %s",
362 slavename);
363 adjust_file_limit (pty: slavename);
364 /* wait until in a new mount ns to open the slave */
365
366 /* enable `wait`ing on grandchildren */
367 VERIFY (prctl (PR_SET_CHILD_SUBREAPER, 1) == 0);
368
369 pid_t pid = xfork (); /* outer child */
370 if (pid == 0)
371 {
372 xclose (master);
373 xclose (pid_pipe[0]);
374 xclose (exit_pipe[1]);
375
376 if (!support_enter_mount_namespace ())
377 FAIL_UNSUPPORTED ("could not enter new mount namespace");
378
379 int slave = xopen (path: slavename, O_RDWR, 0);
380 if (!doit (fd: slave, testname: "basic smoketest",
381 expected_r: (struct result_r){.name=slavename, .ret=0, .err=0}))
382 _exit (1);
383
384 VERIFY (mount ("tmpfs", chrootdir, "tmpfs", 0, "mode=755") == 0);
385 VERIFY (chdir (chrootdir) == 0);
386
387 xmkdir (path: "proc", 0755);
388 xmkdir (path: "dev", 0755);
389 xmkdir (path: "dev/pts", 0755);
390
391 VERIFY (mount ("devpts", "dev/pts", "devpts",
392 MS_NOSUID|MS_NOEXEC,
393 "newinstance,ptmxmode=0666,mode=620") == 0);
394 VERIFY (symlink ("pts/ptmx", "dev/ptmx") == 0);
395
396 touch (path: "console", mode: 0);
397 touch (path: "dev/console", mode: 0);
398 VERIFY (mount (slavename, "console", NULL, MS_BIND, NULL) == 0);
399
400 xchroot (path: ".");
401
402 if (unshare (CLONE_NEWNS | CLONE_NEWPID) < 0)
403 FAIL_UNSUPPORTED ("could not enter new PID namespace");
404 pid = xfork (); /* inner child */
405 if (pid == 0)
406 {
407 xclose (pid_pipe[1]);
408
409 /* wait until the outer child has exited */
410 char c;
411 VERIFY (read (exit_pipe[0], &c, 1) == 0);
412 xclose (exit_pipe[0]);
413
414 VERIFY (mount ("proc", "/proc", "proc",
415 MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) == 0);
416
417 char *linkname = xasprintf (format: "/proc/self/fd/%d", slave);
418 char *target = proc_fd_readlink (linkname);
419 VERIFY (strcmp (target, strrchr (slavename, '/')) == 0);
420 free (ptr: linkname);
421
422 _exit (cb (slavename, slave));
423 }
424 xwrite (pid_pipe[1], &pid, sizeof pid);
425 _exit (0);
426 }
427 xclose (pid_pipe[1]);
428 xclose (exit_pipe[0]);
429 xclose (exit_pipe[1]);
430
431 /* wait for the outer child */
432 int status;
433 xwaitpid (pid, status: &status, flags: 0);
434 VERIFY (WIFEXITED (status));
435 int ret = WEXITSTATUS (status);
436 if (ret != 0)
437 return ret;
438
439 /* set 'pid' to the inner child */
440 VERIFY (read (pid_pipe[0], &pid, sizeof pid) == sizeof pid);
441 xclose (pid_pipe[0]);
442
443 /* wait for the inner child */
444 xwaitpid (pid, status: &status, flags: 0);
445 VERIFY (WIFEXITED (status));
446 xclose (master);
447 return WEXITSTATUS (status);
448}
449
450/* main test */
451
452static int
453run_chroot_tests (const char *slavename, int slave)
454{
455 struct stat st;
456 bool ok = true;
457
458 /* There are 3 groups of tests here. The first group fairly
459 generically does things known to mess up ttyname, and verifies
460 that ttyname copes correctly. The remaining groups are
461 increasingly convoluted, as we target specific parts of ttyname
462 to try to confuse. */
463
464 /* Basic tests that it doesn't get confused by multiple devpts
465 instances. */
466 {
467 VERIFY (stat (slavename, &st) < 0); /* sanity check */
468 if (!doit (fd: slave, testname: "no conflict, no match",
469 expected_r: (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
470 ok = false;
471 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
472 if (!doit (fd: slave, testname: "no conflict, console",
473 expected_r: (struct result_r){.name="/dev/console", .ret=0, .err=0}))
474 ok = false;
475 VERIFY (umount ("/dev/console") == 0);
476
477 /* Keep creating PTYs until we we get a name collision. */
478 while (true)
479 {
480 if (stat (file: slavename, buf: &st) == 0)
481 break;
482 if (posix_openpt (O_RDWR|O_NOCTTY|O_NONBLOCK) < 0)
483 {
484 if (errno == ENOSPC || errno == EMFILE || errno == ENFILE)
485 FAIL_UNSUPPORTED ("cannot re-create PTY \"%s\" in chroot: %m"
486 " (consider increasing limits)", slavename);
487 else
488 FAIL_EXIT1 ("cannot re-create PTY \"%s\" chroot: %m", slavename);
489 }
490 }
491
492 if (!doit (fd: slave, testname: "conflict, no match",
493 expected_r: (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
494 ok = false;
495 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
496 if (!doit (fd: slave, testname: "conflict, console",
497 expected_r: (struct result_r){.name="/dev/console", .ret=0, .err=0}))
498 ok = false;
499 VERIFY (umount ("/dev/console") == 0);
500 }
501
502 /* The first tests kinda assumed that they hit certain code-paths
503 based on assuming that the readlink target is 'slavename', but
504 that's not quite always true. They're still a good preliminary
505 sanity check, so keep them, but let's add tests that make sure
506 that those code-paths are hit by doing a readlink ourself. */
507 {
508 char *linkname = xasprintf (format: "/proc/self/fd/%d", slave);
509 char *target = proc_fd_readlink (linkname);
510 free (ptr: linkname);
511 /* Depeding on how we set up the chroot, the kernel may or may not
512 trim the leading path to the target (it may give us "/6",
513 instead of "/dev/pts/6"). We test it both ways (do_in_chroot_1
514 and do_in_chroot_2). This test group relies on the target
515 existing, so guarantee that it does exist by creating it if
516 necessary. */
517 if (stat (file: target, buf: &st) < 0)
518 {
519 VERIFY (errno == ENOENT);
520 touch (path: target, mode: 0);
521 }
522
523 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
524 VERIFY (mount ("/console", target, NULL, MS_BIND, NULL) == 0);
525 if (!doit (fd: slave, testname: "with readlink target",
526 expected_r: (struct result_r){.name=target, .ret=0, .err=0}))
527 ok = false;
528 VERIFY (umount (target) == 0);
529 VERIFY (umount ("/dev/console") == 0);
530
531 VERIFY (mount ("/console", "/dev/console", NULL, MS_BIND, NULL) == 0);
532 VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
533 if (!doit (fd: slave, testname: "with readlink trap; fallback",
534 expected_r: (struct result_r){.name="/dev/console", .ret=0, .err=0}))
535 ok = false;
536 VERIFY (umount (target) == 0);
537 VERIFY (umount ("/dev/console") == 0);
538
539 VERIFY (mount (slavename, target, NULL, MS_BIND, NULL) == 0);
540 if (!doit (fd: slave, testname: "with readlink trap; no fallback",
541 expected_r: (struct result_r){.name=NULL, .ret=ENODEV, .err=ENODEV}))
542 ok = false;
543 VERIFY (umount (target) == 0);
544 }
545
546 /* This test makes sure that everything still works OK if readdir
547 finds a pseudo-match before and/or after the actual match. Now,
548 to do that, we need to control that readdir finds the
549 pseudo-matches before and after the actual match; and there's no
550 good way to control that order in absence of whitebox testing.
551 So, just create 3 files, then use opendir/readdir to see what
552 order they are in, and assign meaning based on that order, not by
553 name; assigning the first to be a pseudo-match, the second to be
554 the actual match, and the third to be a pseudo-match. This
555 assumes that (on tmpfs) ordering within the directory is stable
556 in the absence of modification, which seems reasonably safe. */
557 {
558 /* since we're testing the fallback search, disable the readlink
559 happy-path */
560 VERIFY (umount2 ("/proc", MNT_DETACH) == 0);
561
562 touch (path: "/dev/console1", mode: 0);
563 touch (path: "/dev/console2", mode: 0);
564 touch (path: "/dev/console3", mode: 0);
565
566 char *c[3];
567 int ci = 0;
568 DIR *dirstream = opendir (name: "/dev");
569 VERIFY (dirstream != NULL);
570 struct dirent *d;
571 while ((d = readdir (dirp: dirstream)) != NULL && ci < 3)
572 {
573 if (strcmp (d->d_name, "console1")
574 && strcmp (d->d_name, "console2")
575 && strcmp (d->d_name, "console3") )
576 continue;
577 c[ci++] = xasprintf (format: "/dev/%s", d->d_name);
578 }
579 VERIFY (ci == 3);
580 VERIFY (closedir (dirstream) == 0);
581
582 VERIFY (mount (slavename, c[0], NULL, MS_BIND, NULL) == 0);
583 VERIFY (mount ("/console", c[1], NULL, MS_BIND, NULL) == 0);
584 VERIFY (mount (slavename, c[2], NULL, MS_BIND, NULL) == 0);
585 VERIFY (umount2 ("/dev/pts", MNT_DETACH) == 0);
586 if (!doit (fd: slave, testname: "with search-path trap",
587 expected_r: (struct result_r){.name=c[1], .ret=0, .err=0}))
588 ok = false;
589 for (int i = 0; i < 3; i++)
590 {
591 VERIFY (umount (c[i]) == 0);
592 VERIFY (unlink (c[i]) == 0);
593 free (ptr: c[i]);
594 }
595 }
596
597 return ok ? 0 : 1;
598}
599
600static int
601do_test (void)
602{
603 support_become_root ();
604
605 int ret1 = do_in_chroot_1 (cb: run_chroot_tests);
606 if (ret1 == EXIT_UNSUPPORTED)
607 return ret1;
608
609 int ret2 = do_in_chroot_2 (cb: run_chroot_tests);
610 if (ret2 == EXIT_UNSUPPORTED)
611 return ret2;
612
613 return ret1 | ret2;
614}
615
616#include <support/test-driver.c>
617

source code of glibc/sysdeps/unix/sysv/linux/tst-ttyname.c