tst-ttyname.c 18 KB

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