]>
Commit | Line | Data |
---|---|---|
cc73685d | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
6400238d | 2 | |
1160ce89 CB |
3 | #include "config.h" |
4 | ||
6400238d CB |
5 | #include <errno.h> |
6 | #include <stdio.h> | |
7 | #include <stdlib.h> | |
8 | #include <string.h> | |
cee55b59 | 9 | #include <unistd.h> |
6400238d | 10 | |
6400238d | 11 | #include "file_utils.h" |
cee55b59 CB |
12 | #include "macro.h" |
13 | #include "memory_utils.h" | |
f40988c7 | 14 | #include "process_utils.h" |
59eac805 | 15 | #include "rexec.h" |
6400238d CB |
16 | #include "string_utils.h" |
17 | #include "syscall_wrappers.h" | |
18 | ||
61c40023 | 19 | #if IS_BIONIC && !HAVE_FEXECVE |
58db1a61 | 20 | #include "fexecve.h" |
9d361e0f CB |
21 | #endif |
22 | ||
6400238d CB |
23 | #define LXC_MEMFD_REXEC_SEALS \ |
24 | (F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) | |
25 | ||
7a80606d CB |
26 | static int push_vargs(char *data, int data_length, char ***output) |
27 | { | |
28 | int num = 0; | |
29 | char *cur = data; | |
30 | ||
31 | if (!data || *output) | |
32 | return -1; | |
33 | ||
34 | *output = must_realloc(NULL, sizeof(**output)); | |
35 | ||
36 | while (cur < data + data_length) { | |
37 | num++; | |
38 | *output = must_realloc(*output, (num + 1) * sizeof(**output)); | |
39 | ||
40 | (*output)[num - 1] = cur; | |
41 | cur += strlen(cur) + 1; | |
42 | } | |
43 | (*output)[num] = NULL; | |
44 | return num; | |
45 | } | |
46 | ||
cee55b59 | 47 | static int parse_argv(char ***argv) |
7a80606d | 48 | { |
cee55b59 | 49 | __do_free char *cmdline = NULL; |
7a80606d | 50 | int ret; |
cee55b59 | 51 | size_t cmdline_size; |
7a80606d CB |
52 | |
53 | cmdline = file_to_buf("/proc/self/cmdline", &cmdline_size); | |
54 | if (!cmdline) | |
cee55b59 | 55 | return -1; |
7a80606d CB |
56 | |
57 | ret = push_vargs(cmdline, cmdline_size, argv); | |
58 | if (ret <= 0) | |
cee55b59 | 59 | return -1; |
7a80606d | 60 | |
e4edf5d7 | 61 | move_ptr(cmdline); |
7a80606d | 62 | return 0; |
7a80606d CB |
63 | } |
64 | ||
6400238d CB |
65 | static int is_memfd(void) |
66 | { | |
f62cf1d4 | 67 | __do_close int fd = -EBADF; |
4aa90f60 | 68 | int seals; |
6400238d CB |
69 | |
70 | fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); | |
71 | if (fd < 0) | |
72 | return -ENOTRECOVERABLE; | |
73 | ||
74 | seals = fcntl(fd, F_GET_SEALS); | |
4aa90f60 CB |
75 | if (seals < 0) { |
76 | struct stat s = {0}; | |
77 | ||
78 | if (fstat(fd, &s) == 0) | |
79 | return (s.st_nlink == 0); | |
80 | ||
6400238d | 81 | return -EINVAL; |
4aa90f60 | 82 | } |
6400238d CB |
83 | |
84 | return seals == LXC_MEMFD_REXEC_SEALS; | |
85 | } | |
86 | ||
87 | static void lxc_rexec_as_memfd(char **argv, char **envp, const char *memfd_name) | |
88 | { | |
f62cf1d4 | 89 | __do_close int execfd = -EBADF, fd = -EBADF, memfd = -EBADF, |
67e72461 | 90 | tmpfd = -EBADF; |
4aa90f60 | 91 | int ret; |
5e6e38bd CB |
92 | ssize_t bytes_sent = 0; |
93 | struct stat st = {0}; | |
6400238d CB |
94 | |
95 | memfd = memfd_create(memfd_name, MFD_ALLOW_SEALING | MFD_CLOEXEC); | |
4aa90f60 CB |
96 | if (memfd < 0) { |
97 | char template[PATH_MAX]; | |
98 | ||
608a39c2 CB |
99 | ret = strnprintf(template, sizeof(template), |
100 | P_tmpdir "/.%s_XXXXXX", memfd_name); | |
101 | if (ret < 0) | |
4aa90f60 CB |
102 | return; |
103 | ||
104 | tmpfd = lxc_make_tmpfile(template, true); | |
105 | if (tmpfd < 0) | |
106 | return; | |
107 | ||
108 | ret = fchmod(tmpfd, 0700); | |
109 | if (ret) | |
110 | return; | |
111 | } | |
6400238d CB |
112 | |
113 | fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); | |
114 | if (fd < 0) | |
fed8112d | 115 | return; |
6400238d CB |
116 | |
117 | /* sendfile() handles up to 2GB. */ | |
5e6e38bd CB |
118 | ret = fstat(fd, &st); |
119 | if (ret) | |
120 | return; | |
4aa90f60 | 121 | |
5e6e38bd CB |
122 | while (bytes_sent < st.st_size) { |
123 | ssize_t sent; | |
4aa90f60 | 124 | |
5e6e38bd CB |
125 | sent = lxc_sendfile_nointr(memfd >= 0 ? memfd : tmpfd, fd, NULL, |
126 | st.st_size - bytes_sent); | |
127 | if (sent < 0) { | |
6b69d7f8 CB |
128 | /* |
129 | * Fallback to shoveling data between kernel- and | |
5e6e38bd CB |
130 | * userspace. |
131 | */ | |
6b69d7f8 CB |
132 | if (lseek(fd, 0, SEEK_SET) == (off_t) -1) |
133 | fprintf(stderr, "Failed to seek to beginning of file"); | |
134 | ||
5e6e38bd CB |
135 | if (fd_to_fd(fd, memfd >= 0 ? memfd : tmpfd)) |
136 | break; | |
137 | ||
138 | return; | |
4aa90f60 | 139 | } |
5e6e38bd | 140 | bytes_sent += sent; |
4aa90f60 | 141 | } |
4aa90f60 CB |
142 | close_prot_errno_disarm(fd); |
143 | ||
4aa90f60 | 144 | if (memfd >= 0) { |
5e6e38bd CB |
145 | if (fcntl(memfd, F_ADD_SEALS, LXC_MEMFD_REXEC_SEALS)) |
146 | return; | |
147 | ||
67e72461 | 148 | execfd = move_fd(memfd); |
4aa90f60 | 149 | } else { |
4aa90f60 CB |
150 | char procfd[LXC_PROC_PID_FD_LEN]; |
151 | ||
608a39c2 CB |
152 | ret = strnprintf(procfd, sizeof(procfd), "/proc/self/fd/%d", tmpfd); |
153 | if (ret < 0) | |
4aa90f60 CB |
154 | return; |
155 | ||
156 | execfd = open(procfd, O_PATH | O_CLOEXEC); | |
157 | close_prot_errno_disarm(tmpfd); | |
4aa90f60 | 158 | |
4aa90f60 | 159 | } |
5e6e38bd CB |
160 | if (execfd < 0) |
161 | return; | |
162 | ||
163 | fexecve(execfd, argv, envp); | |
6400238d CB |
164 | } |
165 | ||
cee55b59 CB |
166 | /* |
167 | * Get cheap access to the environment. This must be declared by the user as | |
168 | * mandated by POSIX. The definition is located in unistd.h. | |
169 | */ | |
170 | extern char **environ; | |
171 | ||
7a80606d | 172 | int lxc_rexec(const char *memfd_name) |
6400238d | 173 | { |
0212dc6f | 174 | __do_free_string_list char **argv = NULL; |
6400238d | 175 | int ret; |
6400238d CB |
176 | |
177 | ret = is_memfd(); | |
178 | if (ret < 0 && ret == -ENOTRECOVERABLE) { | |
0212dc6f | 179 | fprintf(stderr, "%s - Failed to determine whether this is a memfd\n", |
6400238d CB |
180 | strerror(errno)); |
181 | return -1; | |
182 | } else if (ret > 0) { | |
183 | return 0; | |
184 | } | |
185 | ||
cee55b59 | 186 | ret = parse_argv(&argv); |
7a80606d | 187 | if (ret < 0) { |
0212dc6f | 188 | fprintf(stderr, "%s - Failed to parse command line parameters\n", |
7a80606d CB |
189 | strerror(errno)); |
190 | return -1; | |
191 | } | |
192 | ||
cee55b59 | 193 | lxc_rexec_as_memfd(argv, environ, memfd_name); |
6400238d CB |
194 | fprintf(stderr, "%s - Failed to rexec as memfd\n", strerror(errno)); |
195 | return -1; | |
196 | } | |
197 | ||
198 | /** | |
199 | * This function will copy any binary that calls liblxc into a memory file and | |
200 | * will use the memfd to rexecute the binary. This is done to prevent attacks | |
201 | * through the /proc/self/exe symlink to corrupt the host binary when host and | |
202 | * container are in the same user namespace or have set up an identity id | |
203 | * mapping: CVE-2019-5736. | |
204 | */ | |
7a80606d | 205 | __attribute__((constructor)) static void liblxc_rexec(void) |
6400238d | 206 | { |
7a80606d | 207 | if (getenv("LXC_MEMFD_REXEC") && lxc_rexec("liblxc")) { |
6400238d CB |
208 | fprintf(stderr, "Failed to re-execute liblxc via memory file descriptor\n"); |
209 | _exit(EXIT_FAILURE); | |
210 | } | |
211 | } |