inc.c 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // SPDX-License-Identifier: BSD-3-Clause
  2. /*
  3. * Very simple script interpreter that can evaluate two different commands (one
  4. * per line):
  5. * - "?" to initialize a counter from user's input;
  6. * - "+" to increment the counter (which is set to 0 by default).
  7. *
  8. * See tools/testing/selftests/exec/check-exec-tests.sh and
  9. * Documentation/userspace-api/check_exec.rst
  10. *
  11. * Copyright © 2024 Microsoft Corporation
  12. */
  13. #define _GNU_SOURCE
  14. #include <errno.h>
  15. #include <linux/fcntl.h>
  16. #include <linux/prctl.h>
  17. #include <linux/securebits.h>
  18. #include <stdbool.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <sys/prctl.h>
  23. #include <sys/syscall.h>
  24. #include <unistd.h>
  25. static int sys_execveat(int dirfd, const char *pathname, char *const argv[],
  26. char *const envp[], int flags)
  27. {
  28. return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
  29. }
  30. /* Returns 1 on error, 0 otherwise. */
  31. static int interpret_buffer(char *buffer, size_t buffer_size)
  32. {
  33. char *line, *saveptr = NULL;
  34. long long number = 0;
  35. /* Each command is the first character of a line. */
  36. saveptr = NULL;
  37. line = strtok_r(buffer, "\n", &saveptr);
  38. while (line) {
  39. if (*line != '#' && strlen(line) != 1) {
  40. fprintf(stderr, "# ERROR: Unknown string\n");
  41. return 1;
  42. }
  43. switch (*line) {
  44. case '#':
  45. /* Skips shebang and comments. */
  46. break;
  47. case '+':
  48. /* Increments and prints the number. */
  49. number++;
  50. printf("%lld\n", number);
  51. break;
  52. case '?':
  53. /* Reads integer from stdin. */
  54. fprintf(stderr, "> Enter new number: \n");
  55. if (scanf("%lld", &number) != 1) {
  56. fprintf(stderr,
  57. "# WARNING: Failed to read number from stdin\n");
  58. }
  59. break;
  60. default:
  61. fprintf(stderr, "# ERROR: Unknown character '%c'\n",
  62. *line);
  63. return 1;
  64. }
  65. line = strtok_r(NULL, "\n", &saveptr);
  66. }
  67. return 0;
  68. }
  69. /* Returns 1 on error, 0 otherwise. */
  70. static int interpret_stream(FILE *script, char *const script_name,
  71. char *const *const envp, const bool restrict_stream)
  72. {
  73. int err;
  74. char *const script_argv[] = { script_name, NULL };
  75. char buf[128] = {};
  76. size_t buf_size = sizeof(buf);
  77. /*
  78. * We pass a valid argv and envp to the kernel to emulate a native
  79. * script execution. We must use the script file descriptor instead of
  80. * the script path name to avoid race conditions.
  81. */
  82. err = sys_execveat(fileno(script), "", script_argv, envp,
  83. AT_EMPTY_PATH | AT_EXECVE_CHECK);
  84. if (err && restrict_stream) {
  85. perror("ERROR: Script execution check");
  86. return 1;
  87. }
  88. /* Reads script. */
  89. buf_size = fread(buf, 1, buf_size - 1, script);
  90. return interpret_buffer(buf, buf_size);
  91. }
  92. static void print_usage(const char *argv0)
  93. {
  94. fprintf(stderr, "usage: %s <script.inc> | -i | -c <command>\n\n",
  95. argv0);
  96. fprintf(stderr, "Example:\n");
  97. fprintf(stderr, " ./set-exec -fi -- ./inc -i < script-exec.inc\n");
  98. }
  99. int main(const int argc, char *const argv[], char *const *const envp)
  100. {
  101. int opt;
  102. char *cmd = NULL;
  103. char *script_name = NULL;
  104. bool interpret_stdin = false;
  105. FILE *script_file = NULL;
  106. int secbits;
  107. bool deny_interactive, restrict_file;
  108. size_t arg_nb;
  109. secbits = prctl(PR_GET_SECUREBITS);
  110. if (secbits == -1) {
  111. /*
  112. * This should never happen, except with a buggy seccomp
  113. * filter.
  114. */
  115. perror("ERROR: Failed to get securebits");
  116. return 1;
  117. }
  118. deny_interactive = !!(secbits & SECBIT_EXEC_DENY_INTERACTIVE);
  119. restrict_file = !!(secbits & SECBIT_EXEC_RESTRICT_FILE);
  120. while ((opt = getopt(argc, argv, "c:i")) != -1) {
  121. switch (opt) {
  122. case 'c':
  123. if (cmd) {
  124. fprintf(stderr, "ERROR: Command already set");
  125. return 1;
  126. }
  127. cmd = optarg;
  128. break;
  129. case 'i':
  130. interpret_stdin = true;
  131. break;
  132. default:
  133. print_usage(argv[0]);
  134. return 1;
  135. }
  136. }
  137. /* Checks that only one argument is used, or read stdin. */
  138. arg_nb = !!cmd + !!interpret_stdin;
  139. if (arg_nb == 0 && argc == 2) {
  140. script_name = argv[1];
  141. } else if (arg_nb != 1) {
  142. print_usage(argv[0]);
  143. return 1;
  144. }
  145. if (cmd) {
  146. /*
  147. * Other kind of interactive interpretations should be denied
  148. * as well (e.g. CLI arguments passing script snippets,
  149. * environment variables interpreted as script). However, any
  150. * way to pass script files should only be restricted according
  151. * to restrict_file.
  152. */
  153. if (deny_interactive) {
  154. fprintf(stderr,
  155. "ERROR: Interactive interpretation denied.\n");
  156. return 1;
  157. }
  158. return interpret_buffer(cmd, strlen(cmd));
  159. }
  160. if (interpret_stdin && !script_name) {
  161. script_file = stdin;
  162. /*
  163. * As for any execve(2) call, this path may be logged by the
  164. * kernel.
  165. */
  166. script_name = "/proc/self/fd/0";
  167. /*
  168. * When stdin is used, it can point to a regular file or a
  169. * pipe. Restrict stdin execution according to
  170. * SECBIT_EXEC_DENY_INTERACTIVE but always allow executable
  171. * files (which are not considered as interactive inputs).
  172. */
  173. return interpret_stream(script_file, script_name, envp,
  174. deny_interactive);
  175. } else if (script_name && !interpret_stdin) {
  176. /*
  177. * In this sample, we don't pass any argument to scripts, but
  178. * otherwise we would have to forge an argv with such
  179. * arguments.
  180. */
  181. script_file = fopen(script_name, "r");
  182. if (!script_file) {
  183. perror("ERROR: Failed to open script");
  184. return 1;
  185. }
  186. /*
  187. * Restricts file execution according to
  188. * SECBIT_EXEC_RESTRICT_FILE.
  189. */
  190. return interpret_stream(script_file, script_name, envp,
  191. restrict_file);
  192. }
  193. print_usage(argv[0]);
  194. return 1;
  195. }