1 | /* Verify that getcwd returns ERANGE for size 1 byte and does not underflow |
2 | buffer when the CWD is too long and is also a mount target of /. See bug |
3 | #28769 or CVE-2021-3999 for more context. |
4 | Copyright The GNU Toolchain Authors. |
5 | This file is part of the GNU C Library. |
6 | |
7 | The GNU C Library is free software; you can redistribute it and/or |
8 | modify it under the terms of the GNU Lesser General Public |
9 | License as published by the Free Software Foundation; either |
10 | version 2.1 of the License, or (at your option) any later version. |
11 | |
12 | The GNU C Library is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | Lesser General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU Lesser General Public |
18 | License along with the GNU C Library; if not, see |
19 | <https://www.gnu.org/licenses/>. */ |
20 | |
21 | #include <errno.h> |
22 | #include <fcntl.h> |
23 | #include <intprops.h> |
24 | #include <limits.h> |
25 | #include <stdio.h> |
26 | #include <stdlib.h> |
27 | #include <string.h> |
28 | #include <sys/mount.h> |
29 | #include <sys/stat.h> |
30 | #include <sys/types.h> |
31 | #include <sys/wait.h> |
32 | |
33 | #include <sys/socket.h> |
34 | #include <sys/un.h> |
35 | #include <support/check.h> |
36 | #include <support/temp_file.h> |
37 | #include <support/test-driver.h> |
38 | #include <support/xsched.h> |
39 | #include <support/xunistd.h> |
40 | |
41 | static char *base; |
42 | #define BASENAME "tst-getcwd-smallbuff" |
43 | #define MOUNT_NAME "mpoint" |
44 | static int sockfd[2]; |
45 | |
46 | static void |
47 | do_cleanup (void) |
48 | { |
49 | support_chdir_toolong_temp_directory (base); |
50 | TEST_VERIFY_EXIT (rmdir (MOUNT_NAME) == 0); |
51 | free (ptr: base); |
52 | } |
53 | |
54 | static void |
55 | send_fd (const int sock, const int fd) |
56 | { |
57 | struct msghdr msg = {0}; |
58 | union |
59 | { |
60 | struct cmsghdr hdr; |
61 | char buf[CMSG_SPACE (sizeof (int))]; |
62 | } cmsgbuf = {0}; |
63 | struct cmsghdr *cmsg; |
64 | struct iovec vec; |
65 | char ch = 'A'; |
66 | ssize_t n; |
67 | |
68 | msg.msg_control = &cmsgbuf.buf; |
69 | msg.msg_controllen = sizeof (cmsgbuf.buf); |
70 | |
71 | cmsg = CMSG_FIRSTHDR (&msg); |
72 | cmsg->cmsg_len = CMSG_LEN (sizeof (int)); |
73 | cmsg->cmsg_level = SOL_SOCKET; |
74 | cmsg->cmsg_type = SCM_RIGHTS; |
75 | memcpy (CMSG_DATA (cmsg), &fd, sizeof (fd)); |
76 | |
77 | vec.iov_base = &ch; |
78 | vec.iov_len = 1; |
79 | msg.msg_iov = &vec; |
80 | msg.msg_iovlen = 1; |
81 | |
82 | while ((n = sendmsg (fd: sock, message: &msg, flags: 0)) == -1 && errno == EINTR); |
83 | |
84 | TEST_VERIFY_EXIT (n == 1); |
85 | } |
86 | |
87 | static int |
88 | recv_fd (const int sock) |
89 | { |
90 | struct msghdr msg = {0}; |
91 | union |
92 | { |
93 | struct cmsghdr hdr; |
94 | char buf[CMSG_SPACE(sizeof(int))]; |
95 | } cmsgbuf = {0}; |
96 | struct cmsghdr *cmsg; |
97 | struct iovec vec; |
98 | ssize_t n; |
99 | char ch = '\0'; |
100 | int fd = -1; |
101 | |
102 | vec.iov_base = &ch; |
103 | vec.iov_len = 1; |
104 | msg.msg_iov = &vec; |
105 | msg.msg_iovlen = 1; |
106 | |
107 | msg.msg_control = &cmsgbuf.buf; |
108 | msg.msg_controllen = sizeof (cmsgbuf.buf); |
109 | |
110 | while ((n = recvmsg (fd: sock, message: &msg, flags: 0)) == -1 && errno == EINTR); |
111 | if (n != 1 || ch != 'A') |
112 | return -1; |
113 | |
114 | cmsg = CMSG_FIRSTHDR (&msg); |
115 | if (cmsg == NULL) |
116 | return -1; |
117 | if (cmsg->cmsg_type != SCM_RIGHTS) |
118 | return -1; |
119 | memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd)); |
120 | if (fd < 0) |
121 | return -1; |
122 | return fd; |
123 | } |
124 | |
125 | static int |
126 | child_func (void * const arg) |
127 | { |
128 | xclose (sockfd[0]); |
129 | const int sock = sockfd[1]; |
130 | char ch; |
131 | |
132 | TEST_VERIFY_EXIT (read (sock, &ch, 1) == 1); |
133 | TEST_VERIFY_EXIT (ch == '1'); |
134 | |
135 | if (mount (special_file: "/" , MOUNT_NAME, NULL, MS_BIND | MS_REC, NULL)) |
136 | FAIL_EXIT1 ("mount failed: %m\n" ); |
137 | const int fd = xopen (path: "mpoint" , |
138 | O_RDONLY | O_PATH | O_DIRECTORY | O_NOFOLLOW, 0); |
139 | |
140 | send_fd (sock, fd); |
141 | xclose (fd); |
142 | |
143 | TEST_VERIFY_EXIT (read (sock, &ch, 1) == 1); |
144 | TEST_VERIFY_EXIT (ch == 'a'); |
145 | |
146 | xclose (sock); |
147 | return 0; |
148 | } |
149 | |
150 | static void |
151 | update_map (char * const mapping, const char * const map_file) |
152 | { |
153 | const size_t map_len = strlen (mapping); |
154 | |
155 | const int fd = xopen (path: map_file, O_WRONLY, 0); |
156 | xwrite (fd, mapping, map_len); |
157 | xclose (fd); |
158 | } |
159 | |
160 | static void |
161 | proc_setgroups_write (const long child_pid, const char * const str) |
162 | { |
163 | const size_t str_len = strlen(str); |
164 | |
165 | char setgroups_path[sizeof ("/proc//setgroups" ) + INT_STRLEN_BOUND (long)]; |
166 | |
167 | snprintf (s: setgroups_path, maxlen: sizeof (setgroups_path), |
168 | format: "/proc/%ld/setgroups" , child_pid); |
169 | |
170 | const int fd = open (file: setgroups_path, O_WRONLY); |
171 | |
172 | if (fd < 0) |
173 | { |
174 | TEST_VERIFY_EXIT (errno == ENOENT); |
175 | FAIL_UNSUPPORTED ("/proc/%ld/setgroups not found\n" , child_pid); |
176 | } |
177 | |
178 | xwrite (fd, str, str_len); |
179 | xclose(fd); |
180 | } |
181 | |
182 | static char child_stack[1024 * 1024]; |
183 | |
184 | int |
185 | do_test (void) |
186 | { |
187 | base = support_create_and_chdir_toolong_temp_directory (BASENAME); |
188 | |
189 | xmkdir (MOUNT_NAME, S_IRWXU); |
190 | atexit (func: do_cleanup); |
191 | |
192 | /* Check whether user namespaces are supported. */ |
193 | { |
194 | pid_t pid = xfork (); |
195 | if (pid == 0) |
196 | { |
197 | if (unshare (CLONE_NEWUSER | CLONE_NEWNS) != 0) |
198 | _exit (EXIT_UNSUPPORTED); |
199 | else |
200 | _exit (0); |
201 | } |
202 | int status; |
203 | xwaitpid (pid, status: &status, flags: 0); |
204 | TEST_VERIFY_EXIT (WIFEXITED (status)); |
205 | if (WEXITSTATUS (status) != 0) |
206 | return WEXITSTATUS (status); |
207 | } |
208 | |
209 | TEST_VERIFY_EXIT (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfd) == 0); |
210 | pid_t child_pid = xclone (fn: child_func, NULL, stack: child_stack, |
211 | stack_size: sizeof (child_stack), |
212 | CLONE_NEWUSER | CLONE_NEWNS | SIGCHLD); |
213 | |
214 | xclose (sockfd[1]); |
215 | const int sock = sockfd[0]; |
216 | |
217 | char map_path[sizeof ("/proc//uid_map" ) + INT_STRLEN_BOUND (long)]; |
218 | char map_buf[sizeof ("0 1" ) + INT_STRLEN_BOUND (long)]; |
219 | |
220 | snprintf (s: map_path, maxlen: sizeof (map_path), format: "/proc/%ld/uid_map" , |
221 | (long) child_pid); |
222 | snprintf (s: map_buf, maxlen: sizeof (map_buf), format: "0 %ld 1" , (long) getuid()); |
223 | update_map (mapping: map_buf, map_file: map_path); |
224 | |
225 | proc_setgroups_write (child_pid: (long) child_pid, str: "deny" ); |
226 | snprintf (s: map_path, maxlen: sizeof (map_path), format: "/proc/%ld/gid_map" , |
227 | (long) child_pid); |
228 | snprintf (s: map_buf, maxlen: sizeof (map_buf), format: "0 %ld 1" , (long) getgid()); |
229 | update_map (mapping: map_buf, map_file: map_path); |
230 | |
231 | TEST_VERIFY_EXIT (send (sock, "1" , 1, MSG_NOSIGNAL) == 1); |
232 | const int fd = recv_fd (sock); |
233 | TEST_VERIFY_EXIT (fd >= 0); |
234 | TEST_VERIFY_EXIT (fchdir (fd) == 0); |
235 | |
236 | static char buf[2 * 10 + 1]; |
237 | memset (buf, 'A', sizeof (buf)); |
238 | |
239 | /* Finally, call getcwd and check if it resulted in a buffer underflow. */ |
240 | char * cwd = getcwd (buf: buf + sizeof (buf) / 2, size: 1); |
241 | TEST_VERIFY (cwd == NULL); |
242 | TEST_VERIFY (errno == ERANGE); |
243 | |
244 | for (int i = 0; i < sizeof (buf); i++) |
245 | if (buf[i] != 'A') |
246 | { |
247 | printf (format: "buf[%d] = %02x\n" , i, (unsigned int) buf[i]); |
248 | support_record_failure (); |
249 | } |
250 | |
251 | TEST_VERIFY_EXIT (send (sock, "a" , 1, MSG_NOSIGNAL) == 1); |
252 | xclose (sock); |
253 | TEST_VERIFY_EXIT (xwaitpid (child_pid, NULL, 0) == child_pid); |
254 | |
255 | return 0; |
256 | } |
257 | |
258 | #define CLEANUP_HANDLER do_cleanup |
259 | #include <support/test-driver.c> |
260 | |