]> git.proxmox.com Git - mirror_lxc.git/blob - src/lxc/rexec.c
Merge pull request #4236 from mihalicyn/github_check_fixes
[mirror_lxc.git] / src / lxc / rexec.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "config.h"
4
5 #include <errno.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <unistd.h>
10
11 #include "file_utils.h"
12 #include "macro.h"
13 #include "memory_utils.h"
14 #include "process_utils.h"
15 #include "rexec.h"
16 #include "string_utils.h"
17 #include "syscall_wrappers.h"
18
19 #if IS_BIONIC && !HAVE_FEXECVE
20 #include "fexecve.h"
21 #endif
22
23 #define LXC_MEMFD_REXEC_SEALS \
24 (F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)
25
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
47 static int parse_argv(char ***argv)
48 {
49 __do_free char *cmdline = NULL;
50 int ret;
51 size_t cmdline_size;
52
53 cmdline = file_to_buf("/proc/self/cmdline", &cmdline_size);
54 if (!cmdline)
55 return -1;
56
57 ret = push_vargs(cmdline, cmdline_size, argv);
58 if (ret <= 0)
59 return -1;
60
61 move_ptr(cmdline);
62 return 0;
63 }
64
65 static int is_memfd(void)
66 {
67 __do_close int fd = -EBADF;
68 int seals;
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);
75 if (seals < 0) {
76 struct stat s = {0};
77
78 if (fstat(fd, &s) == 0)
79 return (s.st_nlink == 0);
80
81 return -EINVAL;
82 }
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 {
89 __do_close int execfd = -EBADF, fd = -EBADF, memfd = -EBADF,
90 tmpfd = -EBADF;
91 int ret;
92 ssize_t bytes_sent = 0;
93 struct stat st = {0};
94
95 memfd = memfd_create(memfd_name, MFD_ALLOW_SEALING | MFD_CLOEXEC);
96 if (memfd < 0) {
97 char template[PATH_MAX];
98
99 ret = strnprintf(template, sizeof(template),
100 P_tmpdir "/.%s_XXXXXX", memfd_name);
101 if (ret < 0)
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 }
112
113 fd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC);
114 if (fd < 0)
115 return;
116
117 /* sendfile() handles up to 2GB. */
118 ret = fstat(fd, &st);
119 if (ret)
120 return;
121
122 while (bytes_sent < st.st_size) {
123 ssize_t sent;
124
125 sent = lxc_sendfile_nointr(memfd >= 0 ? memfd : tmpfd, fd, NULL,
126 st.st_size - bytes_sent);
127 if (sent < 0) {
128 /*
129 * Fallback to shoveling data between kernel- and
130 * userspace.
131 */
132 if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
133 fprintf(stderr, "Failed to seek to beginning of file");
134
135 if (fd_to_fd(fd, memfd >= 0 ? memfd : tmpfd))
136 break;
137
138 return;
139 }
140 bytes_sent += sent;
141 }
142 close_prot_errno_disarm(fd);
143
144 if (memfd >= 0) {
145 if (fcntl(memfd, F_ADD_SEALS, LXC_MEMFD_REXEC_SEALS))
146 return;
147
148 execfd = move_fd(memfd);
149 } else {
150 char procfd[LXC_PROC_PID_FD_LEN];
151
152 ret = strnprintf(procfd, sizeof(procfd), "/proc/self/fd/%d", tmpfd);
153 if (ret < 0)
154 return;
155
156 execfd = open(procfd, O_PATH | O_CLOEXEC);
157 close_prot_errno_disarm(tmpfd);
158
159 }
160 if (execfd < 0)
161 return;
162
163 fexecve(execfd, argv, envp);
164 }
165
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
172 int lxc_rexec(const char *memfd_name)
173 {
174 __do_free_string_list char **argv = NULL;
175 int ret;
176
177 ret = is_memfd();
178 if (ret < 0 && ret == -ENOTRECOVERABLE) {
179 fprintf(stderr, "%s - Failed to determine whether this is a memfd\n",
180 strerror(errno));
181 return -1;
182 } else if (ret > 0) {
183 return 0;
184 }
185
186 ret = parse_argv(&argv);
187 if (ret < 0) {
188 fprintf(stderr, "%s - Failed to parse command line parameters\n",
189 strerror(errno));
190 return -1;
191 }
192
193 lxc_rexec_as_memfd(argv, environ, memfd_name);
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 */
205 __attribute__((constructor)) static void liblxc_rexec(void)
206 {
207 if (getenv("LXC_MEMFD_REXEC") && lxc_rexec("liblxc")) {
208 fprintf(stderr, "Failed to re-execute liblxc via memory file descriptor\n");
209 _exit(EXIT_FAILURE);
210 }
211 }