mountfd 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +225 -0
- data/Rakefile +69 -0
- data/benchmark/mounts.rb +34 -0
- data/docs/kernel-notes.md +48 -0
- data/examples/atomic_swap.rb +12 -0
- data/examples/idmapped_volume.rb +17 -0
- data/examples/mini_container.rb +40 -0
- data/examples/readonly_sandbox.rb +39 -0
- data/ext/mountfd/compat.h +197 -0
- data/ext/mountfd/constants.c +53 -0
- data/ext/mountfd/extconf.rb +12 -0
- data/ext/mountfd/mount_info.c +166 -0
- data/ext/mountfd/mountfd.c +393 -0
- data/ext/mountfd/mountfd.h +17 -0
- data/ext/mountfd/namespace.c +74 -0
- data/ext/mountfd/user_namespace.c +220 -0
- data/lib/mountfd/attributes.rb +65 -0
- data/lib/mountfd/core.rb +346 -0
- data/lib/mountfd/mount_info.rb +78 -0
- data/lib/mountfd/namespace.rb +39 -0
- data/lib/mountfd/user_namespace.rb +84 -0
- data/lib/mountfd/version.rb +5 -0
- data/lib/mountfd.rb +32 -0
- data/sig/mountfd.rbs +119 -0
- data/tools/dump_constants.c +54 -0
- data/tools/generate_constants.rb +32 -0
- data/tools/research_idmap.rb +37 -0
- data/tools/vm/Makefile +8 -0
- data/tools/vm/config-fragment +7 -0
- data/tools/vm/run.sh +18 -0
- metadata +118 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "ruby/io.h"
|
|
3
|
+
#include "extconf.h"
|
|
4
|
+
#include "compat.h"
|
|
5
|
+
#include "mountfd.h"
|
|
6
|
+
|
|
7
|
+
#ifdef _WIN32
|
|
8
|
+
# include <io.h>
|
|
9
|
+
# define mountfd_close _close
|
|
10
|
+
#else
|
|
11
|
+
# include <unistd.h>
|
|
12
|
+
# define mountfd_close close
|
|
13
|
+
#endif
|
|
14
|
+
#ifdef __linux__
|
|
15
|
+
# include <errno.h>
|
|
16
|
+
# include <fcntl.h>
|
|
17
|
+
# include <string.h>
|
|
18
|
+
# include <sys/mount.h>
|
|
19
|
+
#endif
|
|
20
|
+
|
|
21
|
+
typedef struct { int fd; } mountfd_handle;
|
|
22
|
+
|
|
23
|
+
static VALUE mMountfd, mNative, cHandle, eError, eUnsupported;
|
|
24
|
+
|
|
25
|
+
static void handle_free(void *ptr)
|
|
26
|
+
{
|
|
27
|
+
mountfd_handle *handle = ptr;
|
|
28
|
+
if (!handle) return;
|
|
29
|
+
if (handle->fd >= 0) mountfd_close(handle->fd);
|
|
30
|
+
xfree(handle);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static size_t handle_size(const void *ptr)
|
|
34
|
+
{
|
|
35
|
+
return ptr ? sizeof(mountfd_handle) : 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static const rb_data_type_t handle_type = {
|
|
39
|
+
"Mountfd::Native::Handle",
|
|
40
|
+
{NULL, handle_free, handle_size, NULL, {NULL}},
|
|
41
|
+
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
static VALUE handle_alloc(VALUE klass)
|
|
45
|
+
{
|
|
46
|
+
mountfd_handle *handle;
|
|
47
|
+
VALUE object = TypedData_Make_Struct(klass, mountfd_handle, &handle_type, handle);
|
|
48
|
+
handle->fd = -1;
|
|
49
|
+
return object;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static mountfd_handle *get_handle(VALUE object)
|
|
53
|
+
{
|
|
54
|
+
mountfd_handle *handle;
|
|
55
|
+
TypedData_Get_Struct(object, mountfd_handle, &handle_type, handle);
|
|
56
|
+
if (handle->fd < 0) rb_raise(eError, "closed file descriptor");
|
|
57
|
+
return handle;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#ifdef __linux__
|
|
61
|
+
static int fd_from(VALUE value)
|
|
62
|
+
{
|
|
63
|
+
if (rb_typeddata_is_kind_of(value, &handle_type)) return get_handle(value)->fd;
|
|
64
|
+
return NUM2INT(value);
|
|
65
|
+
}
|
|
66
|
+
#endif
|
|
67
|
+
|
|
68
|
+
VALUE mountfd_wrap_fd(int fd)
|
|
69
|
+
{
|
|
70
|
+
mountfd_handle *handle;
|
|
71
|
+
VALUE object = handle_alloc(cHandle);
|
|
72
|
+
TypedData_Get_Struct(object, mountfd_handle, &handle_type, handle);
|
|
73
|
+
handle->fd = fd;
|
|
74
|
+
return object;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static VALUE handle_fileno(VALUE self)
|
|
78
|
+
{
|
|
79
|
+
return INT2NUM(get_handle(self)->fd);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static VALUE handle_close(VALUE self)
|
|
83
|
+
{
|
|
84
|
+
mountfd_handle *handle;
|
|
85
|
+
int fd;
|
|
86
|
+
TypedData_Get_Struct(self, mountfd_handle, &handle_type, handle);
|
|
87
|
+
if (handle->fd >= 0) {
|
|
88
|
+
fd = handle->fd;
|
|
89
|
+
handle->fd = -1;
|
|
90
|
+
if (mountfd_close(fd) < 0) rb_sys_fail("close");
|
|
91
|
+
}
|
|
92
|
+
return Qnil;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static VALUE handle_closed(VALUE self)
|
|
96
|
+
{
|
|
97
|
+
mountfd_handle *handle;
|
|
98
|
+
TypedData_Get_Struct(self, mountfd_handle, &handle_type, handle);
|
|
99
|
+
return handle->fd < 0 ? Qtrue : Qfalse;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#ifndef __linux__
|
|
103
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
104
|
+
__attribute__((noreturn))
|
|
105
|
+
#endif
|
|
106
|
+
static void unavailable(void)
|
|
107
|
+
{
|
|
108
|
+
rb_raise(eUnsupported, "the Linux new mount API is unavailable on this platform");
|
|
109
|
+
}
|
|
110
|
+
#endif
|
|
111
|
+
|
|
112
|
+
#ifdef __linux__
|
|
113
|
+
void mountfd_syscall_failed(const char *name)
|
|
114
|
+
{
|
|
115
|
+
int error = errno;
|
|
116
|
+
if (error == ENOSYS || error == EOPNOTSUPP)
|
|
117
|
+
rb_raise(eUnsupported, "%s is not supported by this kernel or filesystem", name);
|
|
118
|
+
if (error == EPERM)
|
|
119
|
+
rb_exc_raise(rb_syserr_new_str(error, rb_sprintf(
|
|
120
|
+
"%s (insufficient privilege or operation disallowed in this namespace)", name
|
|
121
|
+
)));
|
|
122
|
+
rb_syserr_fail(error, name);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static void set_nonblocking_or_close(int fd)
|
|
126
|
+
{
|
|
127
|
+
int flags = fcntl(fd, F_GETFL);
|
|
128
|
+
if (flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) >= 0) return;
|
|
129
|
+
|
|
130
|
+
int error = errno;
|
|
131
|
+
mountfd_close(fd);
|
|
132
|
+
errno = error;
|
|
133
|
+
rb_sys_fail("fcntl");
|
|
134
|
+
}
|
|
135
|
+
#endif
|
|
136
|
+
|
|
137
|
+
static VALUE native_linux_p(VALUE self)
|
|
138
|
+
{
|
|
139
|
+
#ifdef __linux__
|
|
140
|
+
return Qtrue;
|
|
141
|
+
#else
|
|
142
|
+
return Qfalse;
|
|
143
|
+
#endif
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static VALUE native_syscall_available(VALUE self, VALUE name)
|
|
147
|
+
{
|
|
148
|
+
#ifdef __linux__
|
|
149
|
+
const char *value = StringValueCStr(name);
|
|
150
|
+
long result;
|
|
151
|
+
if (strcmp(value, "fsopen") == 0) {
|
|
152
|
+
result = syscall(SYS_fsopen, "__mountfd_probe__", FSOPEN_CLOEXEC);
|
|
153
|
+
if (result >= 0) mountfd_close((int)result);
|
|
154
|
+
} else if (strcmp(value, "mount_setattr") == 0) {
|
|
155
|
+
result = syscall(SYS_mount_setattr, -1, "", 0, NULL, 0);
|
|
156
|
+
} else if (strcmp(value, "statmount") == 0) {
|
|
157
|
+
result = syscall(SYS_statmount, NULL, NULL, 0, 0);
|
|
158
|
+
} else if (strcmp(value, "listmount") == 0) {
|
|
159
|
+
result = syscall(SYS_listmount, NULL, NULL, 0, 0);
|
|
160
|
+
} else {
|
|
161
|
+
rb_raise(rb_eArgError, "unknown syscall: %s", value);
|
|
162
|
+
}
|
|
163
|
+
return result >= 0 || errno != ENOSYS ? Qtrue : Qfalse;
|
|
164
|
+
#else
|
|
165
|
+
return Qfalse;
|
|
166
|
+
#endif
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
static VALUE native_fsopen(VALUE self, VALUE fsname, VALUE flags)
|
|
170
|
+
{
|
|
171
|
+
#ifdef __linux__
|
|
172
|
+
unsigned int raw_flags = NUM2UINT(flags);
|
|
173
|
+
const char *name = StringValueCStr(fsname);
|
|
174
|
+
int fd = (int)syscall(SYS_fsopen, name, raw_flags);
|
|
175
|
+
RB_GC_GUARD(fsname);
|
|
176
|
+
if (fd < 0) mountfd_syscall_failed("fsopen");
|
|
177
|
+
set_nonblocking_or_close(fd);
|
|
178
|
+
return mountfd_wrap_fd(fd);
|
|
179
|
+
#else
|
|
180
|
+
unavailable(); return Qnil;
|
|
181
|
+
#endif
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static VALUE native_fsconfig(VALUE self, VALUE handle, VALUE command, VALUE key,
|
|
185
|
+
VALUE value, VALUE aux)
|
|
186
|
+
{
|
|
187
|
+
#ifdef __linux__
|
|
188
|
+
int fd = fd_from(handle);
|
|
189
|
+
unsigned int cmd = NUM2UINT(command);
|
|
190
|
+
int raw_aux = NUM2INT(aux);
|
|
191
|
+
const char *key_ptr;
|
|
192
|
+
const void *value_ptr;
|
|
193
|
+
if (!NIL_P(key)) StringValueCStr(key);
|
|
194
|
+
if (!NIL_P(value)) {
|
|
195
|
+
if (cmd == FSCONFIG_SET_BINARY) StringValue(value);
|
|
196
|
+
else StringValueCStr(value);
|
|
197
|
+
}
|
|
198
|
+
if (cmd == FSCONFIG_SET_BINARY &&
|
|
199
|
+
(raw_aux < 0 || (NIL_P(value) ? raw_aux != 0 : raw_aux > RSTRING_LEN(value))))
|
|
200
|
+
rb_raise(rb_eArgError, "binary fsconfig length must be between zero and the value size");
|
|
201
|
+
key_ptr = NIL_P(key) ? NULL : RSTRING_PTR(key);
|
|
202
|
+
if (NIL_P(value)) value_ptr = NULL;
|
|
203
|
+
else value_ptr = RSTRING_PTR(value);
|
|
204
|
+
if (syscall(SYS_fsconfig, fd, cmd, key_ptr, value_ptr, raw_aux) < 0) {
|
|
205
|
+
int error = errno;
|
|
206
|
+
RB_GC_GUARD(key);
|
|
207
|
+
RB_GC_GUARD(value);
|
|
208
|
+
errno = error;
|
|
209
|
+
mountfd_syscall_failed("fsconfig");
|
|
210
|
+
}
|
|
211
|
+
RB_GC_GUARD(key);
|
|
212
|
+
RB_GC_GUARD(value);
|
|
213
|
+
return Qnil;
|
|
214
|
+
#else
|
|
215
|
+
unavailable(); return Qnil;
|
|
216
|
+
#endif
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
static VALUE native_fsmount(VALUE self, VALUE handle, VALUE flags, VALUE attrs)
|
|
220
|
+
{
|
|
221
|
+
#ifdef __linux__
|
|
222
|
+
int context_fd = fd_from(handle);
|
|
223
|
+
unsigned int raw_flags = NUM2UINT(flags);
|
|
224
|
+
unsigned int raw_attrs = NUM2UINT(attrs);
|
|
225
|
+
int fd = (int)syscall(SYS_fsmount, context_fd, raw_flags, raw_attrs);
|
|
226
|
+
if (fd < 0) mountfd_syscall_failed("fsmount");
|
|
227
|
+
return mountfd_wrap_fd(fd);
|
|
228
|
+
#else
|
|
229
|
+
unavailable(); return Qnil;
|
|
230
|
+
#endif
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static VALUE native_fspick(VALUE self, VALUE dfd, VALUE path, VALUE flags)
|
|
234
|
+
{
|
|
235
|
+
#ifdef __linux__
|
|
236
|
+
int directory_fd = NUM2INT(dfd);
|
|
237
|
+
unsigned int raw_flags = NUM2UINT(flags);
|
|
238
|
+
const char *raw_path = StringValueCStr(path);
|
|
239
|
+
int fd = (int)syscall(SYS_fspick, directory_fd, raw_path, raw_flags);
|
|
240
|
+
RB_GC_GUARD(path);
|
|
241
|
+
if (fd < 0) mountfd_syscall_failed("fspick");
|
|
242
|
+
set_nonblocking_or_close(fd);
|
|
243
|
+
return mountfd_wrap_fd(fd);
|
|
244
|
+
#else
|
|
245
|
+
unavailable(); return Qnil;
|
|
246
|
+
#endif
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static VALUE native_open_tree(VALUE self, VALUE dfd, VALUE path, VALUE flags)
|
|
250
|
+
{
|
|
251
|
+
#ifdef __linux__
|
|
252
|
+
int directory_fd = NUM2INT(dfd);
|
|
253
|
+
unsigned int raw_flags = NUM2UINT(flags);
|
|
254
|
+
const char *raw_path = StringValueCStr(path);
|
|
255
|
+
int fd = (int)syscall(SYS_open_tree, directory_fd, raw_path, raw_flags);
|
|
256
|
+
RB_GC_GUARD(path);
|
|
257
|
+
if (fd < 0) mountfd_syscall_failed("open_tree");
|
|
258
|
+
return mountfd_wrap_fd(fd);
|
|
259
|
+
#else
|
|
260
|
+
unavailable(); return Qnil;
|
|
261
|
+
#endif
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
static VALUE native_move_mount(VALUE self, VALUE from_dfd, VALUE from_path,
|
|
265
|
+
VALUE to_dfd, VALUE to_path, VALUE flags)
|
|
266
|
+
{
|
|
267
|
+
#ifdef __linux__
|
|
268
|
+
int source_fd = fd_from(from_dfd);
|
|
269
|
+
int target_fd = NUM2INT(to_dfd);
|
|
270
|
+
unsigned int raw_flags = NUM2UINT(flags);
|
|
271
|
+
const char *source_path, *target_path;
|
|
272
|
+
StringValueCStr(from_path);
|
|
273
|
+
StringValueCStr(to_path);
|
|
274
|
+
source_path = RSTRING_PTR(from_path);
|
|
275
|
+
target_path = RSTRING_PTR(to_path);
|
|
276
|
+
if (syscall(SYS_move_mount, source_fd, source_path, target_fd, target_path, raw_flags) < 0) {
|
|
277
|
+
int error = errno;
|
|
278
|
+
RB_GC_GUARD(from_path);
|
|
279
|
+
RB_GC_GUARD(to_path);
|
|
280
|
+
errno = error;
|
|
281
|
+
mountfd_syscall_failed("move_mount");
|
|
282
|
+
}
|
|
283
|
+
RB_GC_GUARD(from_path);
|
|
284
|
+
RB_GC_GUARD(to_path);
|
|
285
|
+
return Qnil;
|
|
286
|
+
#else
|
|
287
|
+
unavailable(); return Qnil;
|
|
288
|
+
#endif
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
static VALUE native_mount_setattr(VALUE self, VALUE dfd, VALUE path, VALUE flags,
|
|
292
|
+
VALUE attr_set, VALUE attr_clr, VALUE propagation,
|
|
293
|
+
VALUE userns_fd)
|
|
294
|
+
{
|
|
295
|
+
#ifdef __linux__
|
|
296
|
+
int directory_fd = fd_from(dfd);
|
|
297
|
+
unsigned int raw_flags = NUM2UINT(flags);
|
|
298
|
+
uint64_t raw_set = NUM2ULL(attr_set);
|
|
299
|
+
uint64_t raw_clr = NUM2ULL(attr_clr);
|
|
300
|
+
uint64_t raw_propagation = NUM2ULL(propagation);
|
|
301
|
+
int has_idmap = (raw_set & MOUNT_ATTR_IDMAP) != 0;
|
|
302
|
+
if ((raw_clr & MOUNT_ATTR_IDMAP) != 0)
|
|
303
|
+
rb_raise(rb_eArgError, "an idmapped mount cannot be cleared");
|
|
304
|
+
if ((has_idmap && NIL_P(userns_fd)) || (!has_idmap && !NIL_P(userns_fd)))
|
|
305
|
+
rb_raise(rb_eArgError, "MOUNT_ATTR_IDMAP and a user namespace fd must be provided together");
|
|
306
|
+
struct mount_attr attr = {
|
|
307
|
+
raw_set, raw_clr, raw_propagation,
|
|
308
|
+
NIL_P(userns_fd) ? 0 : (uint64_t)fd_from(userns_fd)
|
|
309
|
+
};
|
|
310
|
+
const char *raw_path = StringValueCStr(path);
|
|
311
|
+
if (syscall(SYS_mount_setattr, directory_fd, raw_path, raw_flags,
|
|
312
|
+
&attr, MOUNT_ATTR_SIZE_VER0) < 0) {
|
|
313
|
+
int error = errno;
|
|
314
|
+
RB_GC_GUARD(path);
|
|
315
|
+
errno = error;
|
|
316
|
+
mountfd_syscall_failed("mount_setattr");
|
|
317
|
+
}
|
|
318
|
+
RB_GC_GUARD(path);
|
|
319
|
+
return Qnil;
|
|
320
|
+
#else
|
|
321
|
+
unavailable(); return Qnil;
|
|
322
|
+
#endif
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
static VALUE native_umount2(VALUE self, VALUE path, VALUE flags)
|
|
326
|
+
{
|
|
327
|
+
#ifdef __linux__
|
|
328
|
+
int raw_flags = NUM2INT(flags);
|
|
329
|
+
const char *raw_path = StringValueCStr(path);
|
|
330
|
+
if (umount2(raw_path, raw_flags) < 0) {
|
|
331
|
+
int error = errno;
|
|
332
|
+
RB_GC_GUARD(path);
|
|
333
|
+
errno = error;
|
|
334
|
+
mountfd_syscall_failed("umount2");
|
|
335
|
+
}
|
|
336
|
+
RB_GC_GUARD(path);
|
|
337
|
+
return Qnil;
|
|
338
|
+
#else
|
|
339
|
+
unavailable(); return Qnil;
|
|
340
|
+
#endif
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
static VALUE native_read_diagnostics(VALUE self, VALUE handle)
|
|
344
|
+
{
|
|
345
|
+
#ifdef __linux__
|
|
346
|
+
char buffer[4096];
|
|
347
|
+
int fd = fd_from(handle);
|
|
348
|
+
VALUE output = rb_str_new(NULL, 0);
|
|
349
|
+
ssize_t length;
|
|
350
|
+
for (;;) {
|
|
351
|
+
length = read(fd, buffer, sizeof(buffer));
|
|
352
|
+
if (length > 0) rb_str_cat(output, buffer, length);
|
|
353
|
+
else if (length < 0 && errno == EINTR) continue;
|
|
354
|
+
else break;
|
|
355
|
+
}
|
|
356
|
+
if (length < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != ENODATA)
|
|
357
|
+
rb_sys_fail("read(fs_context)");
|
|
358
|
+
return output;
|
|
359
|
+
#else
|
|
360
|
+
unavailable(); return Qnil;
|
|
361
|
+
#endif
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
void mountfd_define_constants(VALUE native);
|
|
365
|
+
|
|
366
|
+
void Init_mountfd(void)
|
|
367
|
+
{
|
|
368
|
+
mMountfd = rb_define_module("Mountfd");
|
|
369
|
+
eError = rb_const_get(mMountfd, rb_intern("Error"));
|
|
370
|
+
eUnsupported = rb_const_get(mMountfd, rb_intern("UnsupportedError"));
|
|
371
|
+
mNative = rb_define_module_under(mMountfd, "Native");
|
|
372
|
+
cHandle = rb_define_class_under(mNative, "Handle", rb_cObject);
|
|
373
|
+
rb_define_alloc_func(cHandle, handle_alloc);
|
|
374
|
+
rb_undef_method(rb_singleton_class(cHandle), "new");
|
|
375
|
+
rb_define_method(cHandle, "fileno", handle_fileno, 0);
|
|
376
|
+
rb_define_method(cHandle, "close", handle_close, 0);
|
|
377
|
+
rb_define_method(cHandle, "closed?", handle_closed, 0);
|
|
378
|
+
rb_define_singleton_method(mNative, "linux?", native_linux_p, 0);
|
|
379
|
+
rb_define_singleton_method(mNative, "syscall_available?", native_syscall_available, 1);
|
|
380
|
+
rb_define_singleton_method(mNative, "fsopen", native_fsopen, 2);
|
|
381
|
+
rb_define_singleton_method(mNative, "fsconfig", native_fsconfig, 5);
|
|
382
|
+
rb_define_singleton_method(mNative, "fsmount", native_fsmount, 3);
|
|
383
|
+
rb_define_singleton_method(mNative, "fspick", native_fspick, 3);
|
|
384
|
+
rb_define_singleton_method(mNative, "open_tree", native_open_tree, 3);
|
|
385
|
+
rb_define_singleton_method(mNative, "move_mount", native_move_mount, 5);
|
|
386
|
+
rb_define_singleton_method(mNative, "mount_setattr", native_mount_setattr, 7);
|
|
387
|
+
rb_define_singleton_method(mNative, "umount2", native_umount2, 2);
|
|
388
|
+
rb_define_singleton_method(mNative, "read_diagnostics", native_read_diagnostics, 1);
|
|
389
|
+
mountfd_define_constants(mNative);
|
|
390
|
+
mountfd_user_namespace_init(mNative);
|
|
391
|
+
mountfd_mount_info_init(mNative);
|
|
392
|
+
mountfd_namespace_init(mNative);
|
|
393
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#ifndef MOUNTFD_H
|
|
2
|
+
#define MOUNTFD_H
|
|
3
|
+
|
|
4
|
+
#include "ruby.h"
|
|
5
|
+
|
|
6
|
+
VALUE mountfd_wrap_fd(int fd);
|
|
7
|
+
#ifdef __linux__
|
|
8
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
9
|
+
__attribute__((noreturn))
|
|
10
|
+
#endif
|
|
11
|
+
void mountfd_syscall_failed(const char *name);
|
|
12
|
+
#endif
|
|
13
|
+
void mountfd_user_namespace_init(VALUE native);
|
|
14
|
+
void mountfd_mount_info_init(VALUE native);
|
|
15
|
+
void mountfd_namespace_init(VALUE native);
|
|
16
|
+
|
|
17
|
+
#endif
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "mountfd.h"
|
|
3
|
+
#include "compat.h"
|
|
4
|
+
|
|
5
|
+
#ifdef __linux__
|
|
6
|
+
# include <errno.h>
|
|
7
|
+
# include <sched.h>
|
|
8
|
+
# include <sys/mount.h>
|
|
9
|
+
# include <unistd.h>
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
static VALUE native_unshare(VALUE self, VALUE flags)
|
|
13
|
+
{
|
|
14
|
+
#ifdef __linux__
|
|
15
|
+
if (unshare(NUM2INT(flags)) < 0) mountfd_syscall_failed("unshare");
|
|
16
|
+
return Qnil;
|
|
17
|
+
#else
|
|
18
|
+
VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
|
|
19
|
+
VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
|
|
20
|
+
rb_raise(error, "namespaces are unavailable on this platform");
|
|
21
|
+
#endif
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static VALUE native_change_propagation(VALUE self, VALUE path, VALUE flags)
|
|
25
|
+
{
|
|
26
|
+
#ifdef __linux__
|
|
27
|
+
unsigned long raw_flags = NUM2ULONG(flags);
|
|
28
|
+
const char *raw_path = StringValueCStr(path);
|
|
29
|
+
if (mount(NULL, raw_path, NULL, raw_flags, NULL) < 0) {
|
|
30
|
+
int error = errno;
|
|
31
|
+
RB_GC_GUARD(path);
|
|
32
|
+
errno = error;
|
|
33
|
+
mountfd_syscall_failed("mount(propagation)");
|
|
34
|
+
}
|
|
35
|
+
RB_GC_GUARD(path);
|
|
36
|
+
return Qnil;
|
|
37
|
+
#else
|
|
38
|
+
VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
|
|
39
|
+
VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
|
|
40
|
+
rb_raise(error, "mount propagation is unavailable on this platform");
|
|
41
|
+
#endif
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static VALUE native_pivot_root(VALUE self, VALUE new_root, VALUE put_old)
|
|
45
|
+
{
|
|
46
|
+
#ifdef __linux__
|
|
47
|
+
const char *root_path, *old_path;
|
|
48
|
+
StringValueCStr(new_root);
|
|
49
|
+
StringValueCStr(put_old);
|
|
50
|
+
root_path = RSTRING_PTR(new_root);
|
|
51
|
+
old_path = RSTRING_PTR(put_old);
|
|
52
|
+
if (syscall(SYS_pivot_root, root_path, old_path) < 0) {
|
|
53
|
+
int error = errno;
|
|
54
|
+
RB_GC_GUARD(new_root);
|
|
55
|
+
RB_GC_GUARD(put_old);
|
|
56
|
+
errno = error;
|
|
57
|
+
mountfd_syscall_failed("pivot_root");
|
|
58
|
+
}
|
|
59
|
+
RB_GC_GUARD(new_root);
|
|
60
|
+
RB_GC_GUARD(put_old);
|
|
61
|
+
return Qnil;
|
|
62
|
+
#else
|
|
63
|
+
VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
|
|
64
|
+
VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
|
|
65
|
+
rb_raise(error, "pivot_root is unavailable on this platform");
|
|
66
|
+
#endif
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void mountfd_namespace_init(VALUE native)
|
|
70
|
+
{
|
|
71
|
+
rb_define_singleton_method(native, "unshare", native_unshare, 1);
|
|
72
|
+
rb_define_singleton_method(native, "change_propagation", native_change_propagation, 2);
|
|
73
|
+
rb_define_singleton_method(native, "pivot_root", native_pivot_root, 2);
|
|
74
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "extconf.h"
|
|
3
|
+
#include "mountfd.h"
|
|
4
|
+
|
|
5
|
+
#ifdef __linux__
|
|
6
|
+
# include <errno.h>
|
|
7
|
+
# include <fcntl.h>
|
|
8
|
+
# include <sched.h>
|
|
9
|
+
# include <signal.h>
|
|
10
|
+
# include <spawn.h>
|
|
11
|
+
# include <stdio.h>
|
|
12
|
+
# include <stdlib.h>
|
|
13
|
+
# include <string.h>
|
|
14
|
+
# include <sys/types.h>
|
|
15
|
+
# include <sys/wait.h>
|
|
16
|
+
# include <unistd.h>
|
|
17
|
+
extern char **environ;
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
#ifdef __linux__
|
|
21
|
+
static int write_all(int fd, const void *data, size_t length)
|
|
22
|
+
{
|
|
23
|
+
const char *cursor = data;
|
|
24
|
+
while (length > 0) {
|
|
25
|
+
ssize_t written = write(fd, cursor, length);
|
|
26
|
+
if (written < 0) {
|
|
27
|
+
if (errno == EINTR) continue;
|
|
28
|
+
return -1;
|
|
29
|
+
}
|
|
30
|
+
cursor += written;
|
|
31
|
+
length -= (size_t)written;
|
|
32
|
+
}
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static int write_proc_file(pid_t pid, const char *name, const char *value)
|
|
37
|
+
{
|
|
38
|
+
char path[64];
|
|
39
|
+
int fd;
|
|
40
|
+
snprintf(path, sizeof(path), "/proc/%ld/%s", (long)pid, name);
|
|
41
|
+
fd = open(path, O_WRONLY | O_CLOEXEC);
|
|
42
|
+
if (fd < 0) return -1;
|
|
43
|
+
if (write_all(fd, value, strlen(value)) < 0) {
|
|
44
|
+
int error = errno;
|
|
45
|
+
close(fd);
|
|
46
|
+
errno = error;
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
49
|
+
return close(fd);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static int run_map_helper(const char *program, pid_t pid, const char *mapping)
|
|
53
|
+
{
|
|
54
|
+
char *copy = strdup(mapping), *saveptr, *token, pid_text[32];
|
|
55
|
+
size_t count = 0, capacity = 16;
|
|
56
|
+
char **arguments = malloc(capacity * sizeof(char *));
|
|
57
|
+
pid_t helper;
|
|
58
|
+
int status;
|
|
59
|
+
if (!copy || !arguments) {
|
|
60
|
+
free(copy); free(arguments); errno = ENOMEM; return -1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
snprintf(pid_text, sizeof(pid_text), "%ld", (long)pid);
|
|
64
|
+
arguments[count++] = (char *)program;
|
|
65
|
+
arguments[count++] = pid_text;
|
|
66
|
+
token = strtok_r(copy, " \t\r\n", &saveptr);
|
|
67
|
+
while (token) {
|
|
68
|
+
if (count + 1 == capacity) {
|
|
69
|
+
capacity *= 2;
|
|
70
|
+
char **grown = realloc(arguments, capacity * sizeof(char *));
|
|
71
|
+
if (!grown) { free(arguments); free(copy); errno = ENOMEM; return -1; }
|
|
72
|
+
arguments = grown;
|
|
73
|
+
}
|
|
74
|
+
arguments[count++] = token;
|
|
75
|
+
token = strtok_r(NULL, " \t\r\n", &saveptr);
|
|
76
|
+
}
|
|
77
|
+
arguments[count] = NULL;
|
|
78
|
+
|
|
79
|
+
int error = posix_spawnp(&helper, program, NULL, NULL, arguments, environ);
|
|
80
|
+
if (error != 0) {
|
|
81
|
+
free(arguments); free(copy); errno = error; return -1;
|
|
82
|
+
}
|
|
83
|
+
while (waitpid(helper, &status, 0) < 0) {
|
|
84
|
+
if (errno == EINTR) continue;
|
|
85
|
+
int error = errno;
|
|
86
|
+
free(arguments); free(copy); errno = error; return -1;
|
|
87
|
+
}
|
|
88
|
+
free(arguments); free(copy);
|
|
89
|
+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { errno = EPERM; return -1; }
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static int configure_maps(pid_t pid, const char *uid_map, const char *gid_map, int helper)
|
|
94
|
+
{
|
|
95
|
+
if (helper) {
|
|
96
|
+
if (run_map_helper("newuidmap", pid, uid_map) < 0) return -1;
|
|
97
|
+
if (write_proc_file(pid, "setgroups", "deny") < 0 && errno != ENOENT && errno != EPERM)
|
|
98
|
+
return -1;
|
|
99
|
+
return run_map_helper("newgidmap", pid, gid_map);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (write_proc_file(pid, "uid_map", uid_map) < 0) return -1;
|
|
103
|
+
if (write_proc_file(pid, "setgroups", "deny") < 0 && errno != ENOENT && errno != EPERM)
|
|
104
|
+
return -1;
|
|
105
|
+
return write_proc_file(pid, "gid_map", gid_map);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static void stop_keeper(pid_t pid, int release_fd)
|
|
109
|
+
{
|
|
110
|
+
if (release_fd >= 0) close(release_fd);
|
|
111
|
+
kill(pid, SIGKILL);
|
|
112
|
+
while (waitpid(pid, NULL, 0) < 0 && errno == EINTR) {}
|
|
113
|
+
}
|
|
114
|
+
#endif
|
|
115
|
+
|
|
116
|
+
static VALUE native_open_handle(VALUE self, VALUE path)
|
|
117
|
+
{
|
|
118
|
+
#ifdef __linux__
|
|
119
|
+
const char *raw_path = StringValueCStr(path);
|
|
120
|
+
int fd = open(raw_path, O_RDONLY | O_CLOEXEC);
|
|
121
|
+
RB_GC_GUARD(path);
|
|
122
|
+
if (fd < 0) rb_sys_fail_str(path);
|
|
123
|
+
return mountfd_wrap_fd(fd);
|
|
124
|
+
#else
|
|
125
|
+
VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
|
|
126
|
+
VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
|
|
127
|
+
rb_raise(error, "user namespaces are unavailable on this platform");
|
|
128
|
+
#endif
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static VALUE native_user_namespace(VALUE self, VALUE uid_value, VALUE gid_value, VALUE helper_value)
|
|
132
|
+
{
|
|
133
|
+
#ifdef __linux__
|
|
134
|
+
const char *uid_map, *gid_map;
|
|
135
|
+
int ready[2], release[2], child_error = 0, namespace_fd = -1;
|
|
136
|
+
pid_t pid;
|
|
137
|
+
char path[64], byte;
|
|
138
|
+
ssize_t length;
|
|
139
|
+
|
|
140
|
+
StringValueCStr(uid_value);
|
|
141
|
+
StringValueCStr(gid_value);
|
|
142
|
+
uid_map = RSTRING_PTR(uid_value);
|
|
143
|
+
gid_map = RSTRING_PTR(gid_value);
|
|
144
|
+
|
|
145
|
+
if (pipe2(ready, O_CLOEXEC) < 0) rb_sys_fail("pipe2");
|
|
146
|
+
if (pipe2(release, O_CLOEXEC) < 0) {
|
|
147
|
+
int error = errno;
|
|
148
|
+
close(ready[0]); close(ready[1]); errno = error; rb_sys_fail("pipe2");
|
|
149
|
+
}
|
|
150
|
+
pid = fork();
|
|
151
|
+
if (pid == 0) {
|
|
152
|
+
close(ready[0]); close(release[1]);
|
|
153
|
+
if (unshare(CLONE_NEWUSER) < 0) child_error = errno;
|
|
154
|
+
write_all(ready[1], &child_error, sizeof(child_error));
|
|
155
|
+
close(ready[1]);
|
|
156
|
+
if (child_error == 0) while (read(release[0], &byte, 1) < 0 && errno == EINTR) {}
|
|
157
|
+
_exit(child_error == 0 ? 0 : 1);
|
|
158
|
+
}
|
|
159
|
+
close(ready[1]); close(release[0]);
|
|
160
|
+
if (pid < 0) {
|
|
161
|
+
close(ready[0]); close(release[1]); rb_sys_fail("fork");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
do { length = read(ready[0], &child_error, sizeof(child_error)); } while (length < 0 && errno == EINTR);
|
|
165
|
+
close(ready[0]);
|
|
166
|
+
if (length != sizeof(child_error) || child_error != 0) {
|
|
167
|
+
int error = child_error ? child_error : EIO;
|
|
168
|
+
stop_keeper(pid, -1); close(release[1]); errno = error;
|
|
169
|
+
mountfd_syscall_failed("unshare(CLONE_NEWUSER)");
|
|
170
|
+
}
|
|
171
|
+
if (configure_maps(pid, uid_map, gid_map, RTEST(helper_value)) < 0) {
|
|
172
|
+
int error = errno;
|
|
173
|
+
stop_keeper(pid, -1); close(release[1]); errno = error; rb_sys_fail("configure user namespace maps");
|
|
174
|
+
}
|
|
175
|
+
snprintf(path, sizeof(path), "/proc/%ld/ns/user", (long)pid);
|
|
176
|
+
namespace_fd = open(path, O_RDONLY | O_CLOEXEC);
|
|
177
|
+
if (namespace_fd < 0) {
|
|
178
|
+
int error = errno;
|
|
179
|
+
stop_keeper(pid, -1); close(release[1]); errno = error; rb_sys_fail("open user namespace");
|
|
180
|
+
}
|
|
181
|
+
stop_keeper(pid, release[1]);
|
|
182
|
+
RB_GC_GUARD(uid_value);
|
|
183
|
+
RB_GC_GUARD(gid_value);
|
|
184
|
+
return mountfd_wrap_fd(namespace_fd);
|
|
185
|
+
#else
|
|
186
|
+
VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
|
|
187
|
+
VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
|
|
188
|
+
rb_raise(error, "user namespaces are unavailable on this platform");
|
|
189
|
+
#endif
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static VALUE native_unshare_user(VALUE self, VALUE map_root_value)
|
|
193
|
+
{
|
|
194
|
+
#ifdef __linux__
|
|
195
|
+
uid_t uid = getuid();
|
|
196
|
+
gid_t gid = getgid();
|
|
197
|
+
char uid_map[64], gid_map[64];
|
|
198
|
+
if (unshare(CLONE_NEWUSER) < 0) mountfd_syscall_failed("unshare(CLONE_NEWUSER)");
|
|
199
|
+
if (!RTEST(map_root_value)) return Qnil;
|
|
200
|
+
|
|
201
|
+
snprintf(uid_map, sizeof(uid_map), "0 %lu 1\n", (unsigned long)uid);
|
|
202
|
+
snprintf(gid_map, sizeof(gid_map), "0 %lu 1\n", (unsigned long)gid);
|
|
203
|
+
if (write_proc_file(getpid(), "uid_map", uid_map) < 0) rb_sys_fail("write uid_map");
|
|
204
|
+
if (write_proc_file(getpid(), "setgroups", "deny") < 0 && errno != ENOENT && errno != EPERM)
|
|
205
|
+
rb_sys_fail("write setgroups");
|
|
206
|
+
if (write_proc_file(getpid(), "gid_map", gid_map) < 0) rb_sys_fail("write gid_map");
|
|
207
|
+
return Qnil;
|
|
208
|
+
#else
|
|
209
|
+
VALUE mountfd = rb_const_get(rb_cObject, rb_intern("Mountfd"));
|
|
210
|
+
VALUE error = rb_const_get(mountfd, rb_intern("UnsupportedError"));
|
|
211
|
+
rb_raise(error, "user namespaces are unavailable on this platform");
|
|
212
|
+
#endif
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
void mountfd_user_namespace_init(VALUE native)
|
|
216
|
+
{
|
|
217
|
+
rb_define_singleton_method(native, "open_handle", native_open_handle, 1);
|
|
218
|
+
rb_define_singleton_method(native, "user_namespace", native_user_namespace, 3);
|
|
219
|
+
rb_define_singleton_method(native, "unshare_user", native_unshare_user, 1);
|
|
220
|
+
}
|