chiark / gitweb /
memfd: map unsealed files as MAP_SHARED
[elogind.git] / src / shared / memfd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23 #include <fcntl.h>
24 #include <sys/ioctl.h>
25 #include <sys/mman.h>
26 #include <sys/prctl.h>
27
28 #include "util.h"
29 #include "bus-label.h"
30 #include "missing.h"
31 #include "memfd.h"
32
33 #include "sd-bus.h"
34
35 struct sd_memfd {
36         int fd;
37         FILE *f;
38 };
39
40 int sd_memfd_new(sd_memfd **m, const char *name) {
41
42         _cleanup_free_ char *g = NULL;
43         sd_memfd *n;
44
45         assert_return(m, -EINVAL);
46
47         if (name) {
48                 /* The kernel side is pretty picky about the character
49                  * set here, let's do the usual bus escaping to deal
50                  * with that. */
51
52                 g = bus_label_escape(name);
53                 if (!g)
54                         return -ENOMEM;
55
56                 name = g;
57
58         } else {
59                 char pr[17] = {};
60
61                 /* If no name is specified we generate one. We include
62                  * a hint indicating our library implementation, and
63                  * add the thread name to it */
64
65                 assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0);
66
67                 if (isempty(pr))
68                         name = "sd";
69                 else {
70                         _cleanup_free_ char *e = NULL;
71
72                         e = bus_label_escape(pr);
73                         if (!e)
74                                 return -ENOMEM;
75
76                         g = strappend("sd-", e);
77                         if (!g)
78                                 return -ENOMEM;
79
80                         name = g;
81                 }
82         }
83
84         n = new0(struct sd_memfd, 1);
85         if (!n)
86                 return -ENOMEM;
87
88         n->fd = memfd_create(name, MFD_ALLOW_SEALING);
89         if (n->fd < 0) {
90                 free(n);
91                 return -errno;
92         }
93
94         *m = n;
95         return 0;
96 }
97
98 int sd_memfd_new_from_fd(sd_memfd **m, int fd) {
99         sd_memfd *n;
100         int r;
101
102         assert_return(m, -EINVAL);
103         assert_return(fd >= 0, -EINVAL);
104
105         /* Check if this is a sealable fd. The kernel sets F_SEAL_SEAL on memfds
106          * that don't support sealing, so check for that, too. A file with
107          * *only* F_SEAL_SEAL set is the same as a random shmem file, so no
108          * reason to allow opening it as memfd. */
109         r = fcntl(fd, F_GET_SEALS);
110         if (r < 0 || r == F_SEAL_SEAL)
111                 return -ENOTTY;
112
113         n = new0(struct sd_memfd, 1);
114         if (!n)
115                 return -ENOMEM;
116
117         n->fd = fd;
118         *m = n;
119
120         return 0;
121 }
122
123 void sd_memfd_free(sd_memfd *m) {
124         if (!m)
125                 return;
126
127         if (m->f)
128                 fclose(m->f);
129         else
130                 safe_close(m->fd);
131
132         free(m);
133 }
134
135 int sd_memfd_get_fd(sd_memfd *m) {
136         assert_return(m, -EINVAL);
137
138         return m->fd;
139 }
140
141 int sd_memfd_get_file(sd_memfd *m, FILE **f) {
142         assert_return(m, -EINVAL);
143         assert_return(f, -EINVAL);
144
145         if (!m->f) {
146                 m->f = fdopen(m->fd, "r+");
147                 if (!m->f)
148                         return -errno;
149         }
150
151         *f = m->f;
152         return 0;
153 }
154
155 int sd_memfd_dup_fd(sd_memfd *m) {
156         int fd;
157
158         assert_return(m, -EINVAL);
159
160         fd = fcntl(m->fd, F_DUPFD_CLOEXEC, 3);
161         if (fd < 0)
162                 return -errno;
163
164         return fd;
165 }
166
167 int sd_memfd_map(sd_memfd *m, uint64_t offset, size_t size, void **p) {
168         void *q;
169         int sealed;
170
171         assert_return(m, -EINVAL);
172         assert_return(size > 0, -EINVAL);
173         assert_return(p, -EINVAL);
174
175         sealed = sd_memfd_get_sealed(m);
176         if (sealed < 0)
177                 return sealed;
178
179         if (sealed)
180                 q = mmap(NULL, size, PROT_READ, MAP_PRIVATE, m->fd, offset);
181         else
182                 q = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, m->fd, offset);
183
184         if (q == MAP_FAILED)
185                 return -errno;
186
187         *p = q;
188         return 0;
189 }
190
191 int sd_memfd_set_sealed(sd_memfd *m) {
192         int r;
193
194         assert_return(m, -EINVAL);
195
196         r = fcntl(m->fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
197         if (r < 0)
198                 return -errno;
199
200         return 0;
201 }
202
203 int sd_memfd_get_sealed(sd_memfd *m) {
204         int r;
205
206         assert_return(m, -EINVAL);
207
208         r = fcntl(m->fd, F_GET_SEALS);
209         if (r < 0)
210                 return -errno;
211
212         return (r & (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE)) ==
213                     (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE);
214 }
215
216 int sd_memfd_get_size(sd_memfd *m, uint64_t *sz) {
217         int r;
218         struct stat stat;
219
220         assert_return(m, -EINVAL);
221         assert_return(sz, -EINVAL);
222
223         r = fstat(m->fd, &stat);
224         if (r < 0)
225                 return -errno;
226
227         *sz = stat.st_size;
228         return r;
229 }
230
231 int sd_memfd_set_size(sd_memfd *m, uint64_t sz) {
232         int r;
233
234         assert_return(m, -EINVAL);
235
236         r = ftruncate(m->fd, sz);
237         if (r < 0)
238                 return -errno;
239
240         return r;
241 }
242
243 int sd_memfd_new_and_map(sd_memfd **m, const char *name, size_t sz, void **p) {
244         sd_memfd *n;
245         int r;
246
247         r = sd_memfd_new(&n, name);
248         if (r < 0)
249                 return r;
250
251         r = sd_memfd_set_size(n, sz);
252         if (r < 0) {
253                 sd_memfd_free(n);
254                 return r;
255         }
256
257         r = sd_memfd_map(n, 0, sz, p);
258         if (r < 0) {
259                 sd_memfd_free(n);
260                 return r;
261         }
262
263         *m = n;
264         return 0;
265 }
266
267 int sd_memfd_get_name(sd_memfd *m, char **name) {
268         char path[sizeof("/proc/self/fd/") + DECIMAL_STR_MAX(int)], buf[FILENAME_MAX+1], *e;
269         const char *delim, *end;
270         _cleanup_free_ char *n = NULL;
271         ssize_t k;
272
273         assert_return(m, -EINVAL);
274         assert_return(name, -EINVAL);
275
276         sprintf(path, "/proc/self/fd/%i", m->fd);
277
278         k = readlink(path, buf, sizeof(buf));
279         if (k < 0)
280                 return -errno;
281
282         if ((size_t) k >= sizeof(buf))
283                 return -E2BIG;
284
285         buf[k] = 0;
286
287         delim = strstr(buf, ":[");
288         if (!delim)
289                 return -EIO;
290
291         delim = strchr(delim + 2, ':');
292         if (!delim)
293                 return -EIO;
294
295         delim++;
296
297         end = strchr(delim, ']');
298         if (!end)
299                 return -EIO;
300
301         n = strndup(delim, end - delim);
302         if (!n)
303                 return -ENOMEM;
304
305         e = bus_label_unescape(n);
306         if (!e)
307                 return -ENOMEM;
308
309         *name = e;
310
311         return 0;
312 }