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