1 | /* Test for (lack of) command execution in wordexp. |
2 | Copyright (C) 1997-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* This test optionally counts PIDs in a PID namespace to detect |
20 | forks. Without kernel support for that, it will merely look at the |
21 | error codes from wordexp to check that no command execution |
22 | happens. */ |
23 | |
24 | #include <sched.h> |
25 | #include <stdbool.h> |
26 | #include <stdio.h> |
27 | #include <stdlib.h> |
28 | #include <support/check.h> |
29 | #include <support/namespace.h> |
30 | #include <support/xunistd.h> |
31 | #include <wordexp.h> |
32 | |
33 | /* Set to true if the test runs in a PID namespace and can therefore |
34 | use next_pid below. */ |
35 | static bool pid_tests_supported; |
36 | |
37 | /* The next PID, as returned from next_pid below. Only meaningful if |
38 | pid_tests_supported. */ |
39 | static pid_t expected_pid; |
40 | |
41 | /* Allocate the next PID and return it. The process is terminated. |
42 | Note that the test itself advances the next PID. */ |
43 | static pid_t |
44 | next_pid (void) |
45 | { |
46 | pid_t pid = xfork (); |
47 | if (pid == 0) |
48 | _exit (0); |
49 | xwaitpid (pid, NULL, flags: 0); |
50 | return pid; |
51 | } |
52 | |
53 | /* Check that evaluating PATTERN with WRDE_NOCMD results in |
54 | EXPECTED_ERROR. */ |
55 | static void |
56 | expect_failure (const char *pattern, int expected_error) |
57 | { |
58 | printf (format: "info: testing pattern: %s\n" , pattern); |
59 | wordexp_t w; |
60 | TEST_COMPARE (wordexp (pattern, &w, WRDE_NOCMD), expected_error); |
61 | if (pid_tests_supported) |
62 | TEST_COMPARE (expected_pid++, next_pid ()); |
63 | } |
64 | |
65 | /* Run all the tests. Invoked with different IFS values. */ |
66 | static void |
67 | run_tests (void) |
68 | { |
69 | /* Integer overflow in division. */ |
70 | { |
71 | static const char *const numbers[] = { |
72 | "0" , |
73 | "1" , |
74 | "65536" , |
75 | "2147483648" , |
76 | "4294967296" |
77 | "9223372036854775808" , |
78 | "18446744073709551616" , |
79 | "170141183460469231731687303715884105728" , |
80 | "340282366920938463463374607431768211456" , |
81 | NULL |
82 | }; |
83 | |
84 | for (const char *const *num = numbers; *num != NULL; ++num) |
85 | { |
86 | wordexp_t w; |
87 | char pattern[256]; |
88 | snprintf (s: pattern, maxlen: sizeof (pattern), format: "$[(-%s)/(-1)]" , *num); |
89 | int ret = wordexp (words: pattern, pwordexp: &w, flags: WRDE_NOCMD); |
90 | if (ret == 0) |
91 | { |
92 | /* If the call is successful, the result must match the |
93 | original number. */ |
94 | TEST_COMPARE (w.we_wordc, 1); |
95 | TEST_COMPARE_STRING (w.we_wordv[0], *num); |
96 | TEST_COMPARE_STRING (w.we_wordv[1], NULL); |
97 | wordfree (&w); |
98 | } |
99 | else |
100 | /* Otherwise, the test must fail with a syntax error. */ |
101 | TEST_COMPARE (ret, WRDE_SYNTAX); |
102 | |
103 | /* In both cases, command execution is not permitted. */ |
104 | if (pid_tests_supported) |
105 | TEST_COMPARE (expected_pid++, next_pid ()); |
106 | } |
107 | } |
108 | |
109 | /* (Lack of) command execution tests. */ |
110 | |
111 | expect_failure (pattern: "$(ls)" , expected_error: WRDE_CMDSUB); |
112 | |
113 | /* Test for CVE-2014-7817. We test 3 combinations of command |
114 | substitution inside an arithmetic expression to make sure that |
115 | no commands are executed and error is returned. */ |
116 | expect_failure (pattern: "$((`echo 1`))" , expected_error: WRDE_CMDSUB); |
117 | expect_failure (pattern: "$((1+`echo 1`))" , expected_error: WRDE_CMDSUB); |
118 | expect_failure (pattern: "$((1+$((`echo 1`))))" , expected_error: WRDE_CMDSUB); |
119 | |
120 | expect_failure (pattern: "$[1/0]" , expected_error: WRDE_SYNTAX); /* BZ 18100. */ |
121 | } |
122 | |
123 | static void |
124 | subprocess (void *closure) |
125 | { |
126 | expected_pid = 2; |
127 | if (pid_tests_supported) |
128 | TEST_COMPARE (expected_pid++, next_pid ()); |
129 | |
130 | /* Check that triggering command execution via wordexp results in a |
131 | PID increase. */ |
132 | if (pid_tests_supported) |
133 | { |
134 | wordexp_t w; |
135 | TEST_COMPARE (wordexp ("$(echo Test)" , &w, 0), 0); |
136 | TEST_COMPARE (w.we_wordc, 1); |
137 | TEST_COMPARE_STRING (w.we_wordv[0], "Test" ); |
138 | TEST_COMPARE_STRING (w.we_wordv[1], NULL); |
139 | wordfree (&w); |
140 | |
141 | pid_t n = next_pid (); |
142 | printf (format: "info: self-test resulted in PID %d (processes created: %d)\n" , |
143 | (int) n, (int) (n - expected_pid)); |
144 | TEST_VERIFY (n > expected_pid); |
145 | expected_pid = n + 1; |
146 | } |
147 | |
148 | puts (s: "info: testing without IFS" ); |
149 | unsetenv (name: "IFS" ); |
150 | run_tests (); |
151 | |
152 | puts (s: "info: testing with IFS" ); |
153 | TEST_COMPARE (setenv ("IFS" , " \t\n" , 1), 0); |
154 | run_tests (); |
155 | } |
156 | |
157 | static int |
158 | do_test (void) |
159 | { |
160 | support_become_root (); |
161 | |
162 | #ifdef CLONE_NEWPID |
163 | if (unshare (CLONE_NEWPID) != 0) |
164 | printf (format: "warning: unshare (CLONE_NEWPID) failed: %m\n" |
165 | "warning: This leads to reduced test coverage.\n" ); |
166 | else |
167 | pid_tests_supported = true; |
168 | #else |
169 | printf ("warning: CLONE_NEWPID not available.\n" |
170 | "warning: This leads to reduced test coverage.\n" ); |
171 | #endif |
172 | |
173 | /* CLONE_NEWPID only has an effect after fork. */ |
174 | support_isolate_in_subprocess (callback: subprocess, NULL); |
175 | |
176 | return 0; |
177 | } |
178 | |
179 | #include <support/test-driver.c> |
180 | |