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