]>
Commit | Line | Data |
---|---|---|
cc73685d | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
6400238d CB |
2 | |
3 | #ifndef _GNU_SOURCE | |
4 | #define _GNU_SOURCE 1 | |
5 | #endif | |
6 | #include <errno.h> | |
7 | #include <stdio.h> | |
8 | #include <stdlib.h> | |
9 | #include <string.h> | |
cee55b59 | 10 | #include <unistd.h> |
6400238d CB |
11 | |
12 | #include "config.h" | |
13 | #include "file_utils.h" | |
cee55b59 CB |
14 | #include "macro.h" |
15 | #include "memory_utils.h" | |
6400238d CB |
16 | #include "raw_syscalls.h" |
17 | #include "string_utils.h" | |
18 | #include "syscall_wrappers.h" | |
19 | ||
9d361e0f CB |
20 | #if IS_BIONIC |
21 | #include "../include/fexecve.h" | |
22 | #endif | |
23 | ||
6400238d CB |
24 | #define LXC_MEMFD_REXEC_SEALS \ |
25 | (F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) | |
26 | ||
7a80606d CB |
27 | static int push_vargs(char *data, int data_length, char ***output) |
28 | { | |
29 | int num = 0; | |
30 | char *cur = data; | |
31 | ||
32 | if (!data || *output) | |
33 | return -1; | |
34 | ||
35 | *output = must_realloc(NULL, sizeof(**output)); | |
36 | ||
37 | while (cur < data + data_length) { | |
38 | num++; | |
39 | *output = must_realloc(*output, (num + 1) * sizeof(**output)); | |
40 | ||
41 | (*output)[num - 1] = cur; | |
42 | cur += strlen(cur) + 1; | |
43 | } | |
44 | (*output)[num] = NULL; | |
45 | return num; | |
46 | } | |
47 | ||
cee55b59 | 48 | static int parse_argv(char ***argv) |
7a80606d | 49 | { |
cee55b59 | 50 | __do_free char *cmdline = NULL; |
7a80606d | 51 | int ret; |
cee55b59 | 52 | size_t cmdline_size; |
7a80606d CB |
53 | |
54 | cmdline = file_to_buf("/proc/self/cmdline", &cmdline_size); | |
55 | if (!cmdline) | |
cee55b59 | 56 | return -1; |
7a80606d CB |
57 | |
58 | ret = push_vargs(cmdline, cmdline_size, argv); | |
59 | if (ret <= 0) | |
cee55b59 | 60 | return -1; |
7a80606d | 61 | |
e4edf5d7 | 62 | move_ptr(cmdline); |
7a80606d | 63 | return 0; |
7a80606d CB |
64 | } |
65 | ||
6400238d CB |
66 | static int is_memfd(void) |
67 | { | |
fed8112d | 68 | __do_close_prot_errno int fd = -EBADF; |
4aa90f60 | 69 | int seals; |
6400238d CB |
70 | |
71 | fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); | |
72 | if (fd < 0) | |
73 | return -ENOTRECOVERABLE; | |
74 | ||
75 | seals = fcntl(fd, F_GET_SEALS); | |
4aa90f60 CB |
76 | if (seals < 0) { |
77 | struct stat s = {0}; | |
78 | ||
79 | if (fstat(fd, &s) == 0) | |
80 | return (s.st_nlink == 0); | |
81 | ||
6400238d | 82 | return -EINVAL; |
4aa90f60 | 83 | } |
6400238d CB |
84 | |
85 | return seals == LXC_MEMFD_REXEC_SEALS; | |
86 | } | |
87 | ||
88 | static void lxc_rexec_as_memfd(char **argv, char **envp, const char *memfd_name) | |
89 | { | |
5e6e38bd CB |
90 | __do_close_prot_errno int execfd = -EBADF, fd = -EBADF, memfd = -EBADF, |
91 | tmpfd = -EBADF; | |
4aa90f60 | 92 | int ret; |
5e6e38bd CB |
93 | ssize_t bytes_sent = 0; |
94 | struct stat st = {0}; | |
6400238d CB |
95 | |
96 | memfd = memfd_create(memfd_name, MFD_ALLOW_SEALING | MFD_CLOEXEC); | |
4aa90f60 CB |
97 | if (memfd < 0) { |
98 | char template[PATH_MAX]; | |
99 | ||
100 | ret = snprintf(template, sizeof(template), | |
101 | P_tmpdir "/.%s_XXXXXX", memfd_name); | |
102 | if (ret < 0 || (size_t)ret >= sizeof(template)) | |
103 | return; | |
104 | ||
105 | tmpfd = lxc_make_tmpfile(template, true); | |
106 | if (tmpfd < 0) | |
107 | return; | |
108 | ||
109 | ret = fchmod(tmpfd, 0700); | |
110 | if (ret) | |
111 | return; | |
112 | } | |
6400238d CB |
113 | |
114 | fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC); | |
115 | if (fd < 0) | |
fed8112d | 116 | return; |
6400238d CB |
117 | |
118 | /* sendfile() handles up to 2GB. */ | |
5e6e38bd CB |
119 | ret = fstat(fd, &st); |
120 | if (ret) | |
121 | return; | |
4aa90f60 | 122 | |
5e6e38bd CB |
123 | while (bytes_sent < st.st_size) { |
124 | ssize_t sent; | |
4aa90f60 | 125 | |
5e6e38bd CB |
126 | sent = lxc_sendfile_nointr(memfd >= 0 ? memfd : tmpfd, fd, NULL, |
127 | st.st_size - bytes_sent); | |
128 | if (sent < 0) { | |
129 | /* Fallback to shoveling data between kernel- and | |
130 | * userspace. | |
131 | */ | |
132 | lseek(fd, 0, SEEK_SET); | |
133 | if (fd_to_fd(fd, memfd >= 0 ? memfd : tmpfd)) | |
134 | break; | |
135 | ||
136 | return; | |
4aa90f60 | 137 | } |
5e6e38bd | 138 | bytes_sent += sent; |
4aa90f60 | 139 | } |
4aa90f60 CB |
140 | close_prot_errno_disarm(fd); |
141 | ||
4aa90f60 | 142 | if (memfd >= 0) { |
5e6e38bd CB |
143 | if (fcntl(memfd, F_ADD_SEALS, LXC_MEMFD_REXEC_SEALS)) |
144 | return; | |
145 | ||
146 | execfd = memfd; | |
4aa90f60 | 147 | } else { |
4aa90f60 CB |
148 | char procfd[LXC_PROC_PID_FD_LEN]; |
149 | ||
150 | ret = snprintf(procfd, sizeof(procfd), "/proc/self/fd/%d", tmpfd); | |
151 | if (ret < 0 || (size_t)ret >= sizeof(procfd)) | |
152 | return; | |
153 | ||
154 | execfd = open(procfd, O_PATH | O_CLOEXEC); | |
155 | close_prot_errno_disarm(tmpfd); | |
4aa90f60 | 156 | |
4aa90f60 | 157 | } |
5e6e38bd CB |
158 | if (execfd < 0) |
159 | return; | |
160 | ||
161 | fexecve(execfd, argv, envp); | |
6400238d CB |
162 | } |
163 | ||
cee55b59 CB |
164 | /* |
165 | * Get cheap access to the environment. This must be declared by the user as | |
166 | * mandated by POSIX. The definition is located in unistd.h. | |
167 | */ | |
168 | extern char **environ; | |
169 | ||
7a80606d | 170 | int lxc_rexec(const char *memfd_name) |
6400238d CB |
171 | { |
172 | int ret; | |
cee55b59 | 173 | char **argv = NULL; |
6400238d CB |
174 | |
175 | ret = is_memfd(); | |
176 | if (ret < 0 && ret == -ENOTRECOVERABLE) { | |
177 | fprintf(stderr, | |
178 | "%s - Failed to determine whether this is a memfd\n", | |
179 | strerror(errno)); | |
180 | return -1; | |
181 | } else if (ret > 0) { | |
182 | return 0; | |
183 | } | |
184 | ||
cee55b59 | 185 | ret = parse_argv(&argv); |
7a80606d CB |
186 | if (ret < 0) { |
187 | fprintf(stderr, | |
188 | "%s - Failed to parse command line parameters\n", | |
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 | } |