rename_attack_test.c 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * Author: Aleksa Sarai <cyphar@cyphar.com>
  4. * Copyright (C) 2018-2019 SUSE LLC.
  5. */
  6. #define _GNU_SOURCE
  7. #include <errno.h>
  8. #include <fcntl.h>
  9. #include <sched.h>
  10. #include <sys/stat.h>
  11. #include <sys/types.h>
  12. #include <sys/mount.h>
  13. #include <sys/mman.h>
  14. #include <sys/prctl.h>
  15. #include <signal.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <stdbool.h>
  19. #include <string.h>
  20. #include <syscall.h>
  21. #include <limits.h>
  22. #include <unistd.h>
  23. #include "../kselftest.h"
  24. #include "helpers.h"
  25. /* Construct a test directory with the following structure:
  26. *
  27. * root/
  28. * |-- a/
  29. * | `-- c/
  30. * `-- b/
  31. */
  32. int setup_testdir(void)
  33. {
  34. int dfd;
  35. char dirname[] = "/tmp/ksft-openat2-rename-attack.XXXXXX";
  36. /* Make the top-level directory. */
  37. if (!mkdtemp(dirname))
  38. ksft_exit_fail_msg("setup_testdir: failed to create tmpdir\n");
  39. dfd = open(dirname, O_PATH | O_DIRECTORY);
  40. if (dfd < 0)
  41. ksft_exit_fail_msg("setup_testdir: failed to open tmpdir\n");
  42. E_mkdirat(dfd, "a", 0755);
  43. E_mkdirat(dfd, "b", 0755);
  44. E_mkdirat(dfd, "a/c", 0755);
  45. return dfd;
  46. }
  47. /* Swap @dirfd/@a and @dirfd/@b constantly. Parent must kill this process. */
  48. pid_t spawn_attack(int dirfd, char *a, char *b)
  49. {
  50. pid_t child = fork();
  51. if (child != 0)
  52. return child;
  53. /* If the parent (the test process) dies, kill ourselves too. */
  54. E_prctl(PR_SET_PDEATHSIG, SIGKILL);
  55. /* Swap @a and @b. */
  56. for (;;)
  57. renameat2(dirfd, a, dirfd, b, RENAME_EXCHANGE);
  58. exit(1);
  59. }
  60. #define NUM_RENAME_TESTS 2
  61. #define ROUNDS 400000
  62. const char *flagname(int resolve)
  63. {
  64. switch (resolve) {
  65. case RESOLVE_IN_ROOT:
  66. return "RESOLVE_IN_ROOT";
  67. case RESOLVE_BENEATH:
  68. return "RESOLVE_BENEATH";
  69. }
  70. return "(unknown)";
  71. }
  72. void test_rename_attack(int resolve)
  73. {
  74. int dfd, afd;
  75. pid_t child;
  76. void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
  77. int escapes = 0, other_errs = 0, exdevs = 0, eagains = 0, successes = 0;
  78. struct open_how how = {
  79. .flags = O_PATH,
  80. .resolve = resolve,
  81. };
  82. if (!openat2_supported) {
  83. how.resolve = 0;
  84. ksft_print_msg("openat2(2) unsupported -- using openat(2) instead\n");
  85. }
  86. dfd = setup_testdir();
  87. afd = openat(dfd, "a", O_PATH);
  88. if (afd < 0)
  89. ksft_exit_fail_msg("test_rename_attack: failed to open 'a'\n");
  90. child = spawn_attack(dfd, "a/c", "b");
  91. for (int i = 0; i < ROUNDS; i++) {
  92. int fd;
  93. char *victim_path = "c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../../c/../..";
  94. if (openat2_supported)
  95. fd = sys_openat2(afd, victim_path, &how);
  96. else
  97. fd = sys_openat(afd, victim_path, &how);
  98. if (fd < 0) {
  99. if (fd == -EAGAIN)
  100. eagains++;
  101. else if (fd == -EXDEV)
  102. exdevs++;
  103. else if (fd == -ENOENT)
  104. escapes++; /* escaped outside and got ENOENT... */
  105. else
  106. other_errs++; /* unexpected error */
  107. } else {
  108. if (fdequal(fd, afd, NULL))
  109. successes++;
  110. else
  111. escapes++; /* we got an unexpected fd */
  112. }
  113. close(fd);
  114. }
  115. if (escapes > 0)
  116. resultfn = ksft_test_result_fail;
  117. ksft_print_msg("non-escapes: EAGAIN=%d EXDEV=%d E<other>=%d success=%d\n",
  118. eagains, exdevs, other_errs, successes);
  119. resultfn("rename attack with %s (%d runs, got %d escapes)\n",
  120. flagname(resolve), ROUNDS, escapes);
  121. /* Should be killed anyway, but might as well make sure. */
  122. E_kill(child, SIGKILL);
  123. }
  124. #define NUM_TESTS NUM_RENAME_TESTS
  125. int main(int argc, char **argv)
  126. {
  127. ksft_print_header();
  128. ksft_set_plan(NUM_TESTS);
  129. test_rename_attack(RESOLVE_BENEATH);
  130. test_rename_attack(RESOLVE_IN_ROOT);
  131. if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
  132. ksft_exit_fail();
  133. else
  134. ksft_exit_pass();
  135. }