-
Notifications
You must be signed in to change notification settings - Fork 237
/
linux.c
358 lines (310 loc) · 14.4 KB
/
linux.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#define _GNU_SOURCE
#include <Python.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <sched.h>
#include <sys/wait.h>
#include <unistd.h>
#define STACK_SIZE 32768
#define LINUX_MODULE_DOC "linux\n"\
"=====\n"\
"The linux module is a simple Python c extension, containing syscall wrappers "\
"missing from the Python os module. You will need to use these system calls "\
"to implement different aspect of process containment during the workshop."
#define PIVOT_ROOT_DOC ".. py:function:: pivot_root(new_root, put_old)\n"\
"\n"\
"change the root filesystem\n"\
"\n"\
":param str new_root: New root file system\n"\
":param str put_old: Directory to move the current process root file system to\n"\
":return: None\n"\
":raises RuntimeError: if pivot_root fails\n"\
"\n"\
"**NOTE:** The following restrictions apply to `new_root` and `put_old`:\n"\
"\n"\
"* They must be directories.\n"\
"* `new_root` and put_old must not be on the same filesystem as the current root.\n"\
"* `new_root` must be a mountpoint.\n"\
"* `put_old` must be underneath `new_root`, that is, adding a nonzero number\n"\
" of /.. to the string pointed to by `put_old` must yield the same directory as\n"\
" `new_root`.\n"\
"* No other filesystem may be mounted on `put_old`.\n"
static PyObject *
pivot_root(PyObject *self, PyObject *args) {
const char *put_old, *new_root;
if (!PyArg_ParseTuple(args, "ss", &new_root, &put_old))
return NULL;
if (syscall(SYS_pivot_root, new_root, put_old) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
} else {
Py_INCREF(Py_None);
return Py_None;
}
}
#define MOUNT_DOC ".. py:function:: mount(source, target, filesystemtype, mountflags, mountopts)\n"\
"\n"\
"mount filesystem\n"\
"\n"\
":param str source: filesystem to attach (can be ``None``)\n"\
":param str target: directory being attached to, or manipulated (in case of flag change)\n"\
":param str filesystemtype: filesystem supported by the kernel (can be ``None``)\n"\
":param int mountflags: any combination (using ``|``) of mount flags supported by mount(2).\n"\
" For the workshop you are most likely to use ``0`` (i.e. no flags), \n"\
" or a combination of: ``linux.MS_REC``, ``linux.MS_PRIVATE``\n"\
":param str mountopts: options passed to the specified filesystem (can be ``None``)\n"\
":return: None\n"\
":raises RuntimeError: if mount fails\n"\
"\n"
static PyObject *
_mount(PyObject *self, PyObject *args) {
const char *source, *target, *filesystemtype, *mountopts;
unsigned long mountflags;
if (!PyArg_ParseTuple(args, "zszkz", &source, &target, &filesystemtype, &mountflags, &mountopts)) {
return NULL;
}
if (mount(source, target, filesystemtype, mountflags, mountopts) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
} else {
Py_INCREF(Py_None);
return Py_None;
}
}
#define UMOUNT_DOC ".. py:function:: umount(target)\n"\
"\n"\
"unmount filesystem\n"\
"\n"\
":param str target: the (topmost) filesystem this directory is mounted on will be removed\n"\
":return: None\n"\
":raises RuntimeError: if umount fails\n"\
"\n"
static PyObject *
_umount(PyObject *self, PyObject *args) {
const char *target;
if (!PyArg_ParseTuple(args, "s", &target)) {
return NULL;
}
if (umount(target) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
} else {
Py_INCREF(Py_None);
return Py_None;
}
}
#define UMOUNT2_DOC ".. py:function:: umount2(target, flags)\n"\
"\n"\
"unmount filesystem but allows additional `flags` controlling the behavior of the operation\n"\
"\n"\
":param str target: the (topmost) filesystem this directory is mounted on will be removed\n"\
":param int flags: control the behavior of the operation. You can combine multiple flags\n"\
" using ``|``. For the workshop you are most likely to use\n"\
" ``linux.MNT_DETACH``\n"\
":return: None\n"\
":raises RuntimeError: if umount2 fails\n"\
"\n"
static PyObject *
_umount2(PyObject *self, PyObject *args) {
const char *target;
int flags;
if (!PyArg_ParseTuple(args, "si", &target, &flags)) {
return NULL;
}
if (umount2(target, flags) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
} else {
Py_INCREF(Py_None);
return Py_None;
}
}
#define UNSHARE_DOC ".. py:function:: unshare(flags)\n"\
"\n"\
"disassociate parts of the process execution context\n"\
"\n"\
":param int flags: which parts of the execution context should be unshared. You can\n"\
" combine multiple flags using ``|``. See below for flags you might want\n"\
" to use in this workshop\n"\
":return: None\n"\
":raises RuntimeError: if unshare fails\n"\
"\n"\
"Useful flags:\n"\
"\n"\
"* ``linux.CLONE_NEWNS`` - Unshare the mount namespace\n"\
"* ``linux.CLONE_NEWUTS`` - Unshare the UTS namespace (hostname, domainname, etc)\n"\
"* ``linux.CLONE_NEWNET`` - Unshare the network namespace\n"\
"* ``linux.CLONE_NEWPID`` - Unshare the PID namespace\n"\
static PyObject *
_unshare(PyObject *self, PyObject *args) {
int clone_flags;
if (!PyArg_ParseTuple(args, "i", &clone_flags))
return NULL;
if (unshare(clone_flags) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
} else {
Py_INCREF(Py_None);
return Py_None;
}
}
#define SETNS_DOC ".. py:function:: setns(fd, nstype)\n"\
"\n"\
"reassociate process with a namespace\n"\
"\n"\
":param int fd: file descriptor referring to a namespace to associate with\n"\
":param int nstype: one of the following: ``0`` (Allow any type of namespace to be joined),\n"\
" ``CLONE_NEWIPC`` (join IPC namespace), ``CLONE_NEWNET`` (join network \n"\
" namespace), or ``CLONE_NEWUTS`` (join UTS namespace)\n"\
":return: None\n"\
":raises RuntimeError: if setns fails\n"\
"\n"\
static PyObject *
_setns(PyObject *self, PyObject *args) {
int fd, nstype;
if (!PyArg_ParseTuple(args, "ii", &fd, &nstype))
return NULL;
if (setns(fd, nstype) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
} else {
Py_INCREF(Py_None);
return Py_None;
}
}
struct py_clone_args {
PyObject *callback;
PyObject *callback_args;
};
static int clone_callback(void *args) {
PyObject *result;
struct py_clone_args *call_args = (struct py_clone_args *)args;
if ((result = PyObject_CallObject(call_args->callback, call_args->callback_args)) == NULL) {
PyErr_Print();
return -1;
} else {
Py_DECREF(result);
}
return 0;
}
#define CLONE_DOC ".. py:function:: clone(callback, flags, callback_args)\n"\
"\n"\
"create a child process\n"\
"\n"\
":param Callable callback: python function to be executed by the forked child\n"\
":param int flags: combination (using ``|``) of flags specifying what should be shared\n"\
" between the calling process and the child process. See below.\n"\
":param tuple callback_args: tuple of arguments for the callback function\n"\
":return: On success, the thread ID of the child process\n"\
":raises RuntimeError: if clone fails\n"\
"\n"\
"\n"\
"Useful flags:\n"\
"\n"\
"* ``linux.CLONE_NEWNS`` - Unshare the mount namespace\n"\
"* ``linux.CLONE_NEWUTS`` - Unshare the UTS namespace (hostname, domainname, etc)\n"\
"* ``linux.CLONE_NEWNET`` - Unshare the network namespace\n"\
"* ``linux.CLONE_NEWPID`` - Unshare the PID namespace\n"\
static PyObject *
_clone(PyObject *self, PyObject *args) {
PyObject *callback, *callback_args;
void *child_stack;
int flags;
pid_t child_pid;
child_stack = malloc(STACK_SIZE);
if (!PyArg_ParseTuple(args, "OiO", &callback, &flags, &callback_args))
return NULL;
if (!PyCallable_Check(callback)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
}
struct py_clone_args call_args;
call_args.callback = callback;
call_args.callback_args = callback_args;
if ((child_pid = clone(&clone_callback, child_stack + STACK_SIZE, flags | SIGCHLD, &call_args)) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return Py_BuildValue("i", -1);
} else {
return Py_BuildValue("i", child_pid);
}
}
#define SETHOSTNAME_DOC ".. py:function:: sethostname(hostname)\n"\
"\n"\
"set the system hostname\n"\
"\n"\
":param str hostname: new hostname value\n"\
":return: None\n"\
":raises RuntimeError: if sethostname fails\n"\
"\n"\
static PyObject *
_sethostname(PyObject *self, PyObject *args) {
const char *hostname;
if (!PyArg_ParseTuple(args, "s", &hostname))
return NULL;
if (sethostname(hostname, strlen(hostname)) == -1) {
PyErr_SetFromErrno(PyExc_RuntimeError);
return NULL;
}
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef LinuxMethods[] = {
{"pivot_root", pivot_root, METH_VARARGS, PIVOT_ROOT_DOC},
{"unshare", _unshare, METH_VARARGS, UNSHARE_DOC},
{"setns", _setns, METH_VARARGS, SETNS_DOC},
{"clone", _clone, METH_VARARGS, CLONE_DOC},
{"sethostname", _sethostname, METH_VARARGS, SETHOSTNAME_DOC},
{"mount", _mount, METH_VARARGS, MOUNT_DOC},
{"umount", _umount, METH_VARARGS, UMOUNT_DOC},
{"umount2", _umount2, METH_VARARGS, UMOUNT2_DOC},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef linuxmodule = {
PyModuleDef_HEAD_INIT,
"linux",
LINUX_MODULE_DOC,
-1,
LinuxMethods
};
PyMODINIT_FUNC
PyInit_linux(void)
{
PyObject *module = PyModule_Create(&linuxmodule);
// clone constants
PyModule_AddIntConstant(module, "CLONE_NEWNS", CLONE_NEWNS); // mount namespace
PyModule_AddIntConstant(module, "CLONE_NEWUTS", CLONE_NEWUTS); // UTS (hostname) namespace
PyModule_AddIntConstant(module, "CLONE_NEWPID", CLONE_NEWPID); // PID namespace
PyModule_AddIntConstant(module, "CLONE_NEWUSER", CLONE_NEWUSER); // users namespace
PyModule_AddIntConstant(module, "CLONE_NEWIPC", CLONE_NEWIPC); // IPC namespace
PyModule_AddIntConstant(module, "CLONE_NEWNET", CLONE_NEWNET); // network namespace
PyModule_AddIntConstant(module, "CLONE_THREAD", CLONE_THREAD);
// mount constants
PyModule_AddIntConstant(module, "MS_RDONLY", MS_RDONLY); /* Mount read-only. */
PyModule_AddIntConstant(module, "MS_NOSUID", MS_NOSUID); /* Ignore suid and sgid bits. */
PyModule_AddIntConstant(module, "MS_NODEV", MS_NODEV); /* Disallow access to device special files. */
PyModule_AddIntConstant(module, "MS_NOEXEC", MS_NOEXEC); /* Disallow program execution. */
PyModule_AddIntConstant(module, "MS_SYNCHRONOUS", MS_SYNCHRONOUS); /* Writes are synced at once. */
PyModule_AddIntConstant(module, "MS_REMOUNT", MS_REMOUNT); /* Alter flags of a mounted FS. */
PyModule_AddIntConstant(module, "MS_MANDLOCK", MS_MANDLOCK); /* Allow mandatory locks on an FS. */
PyModule_AddIntConstant(module, "MS_DIRSYNC", MS_DIRSYNC); /* Directory modifications are synchronous. */
PyModule_AddIntConstant(module, "MS_NOATIME", MS_NOATIME); /* Do not update access times. */
PyModule_AddIntConstant(module, "MS_NODIRATIME", MS_NODIRATIME); /* Do not update directory access times. */
PyModule_AddIntConstant(module, "MS_BIND", MS_BIND); /* Bind directory at different place. */
PyModule_AddIntConstant(module, "MS_MOVE", MS_MOVE);
PyModule_AddIntConstant(module, "MS_REC", MS_REC); /* Recursive loopback */
PyModule_AddIntConstant(module, "MS_SILENT", MS_SILENT);
PyModule_AddIntConstant(module, "MS_POSIXACL", MS_POSIXACL); /* VFS does not apply the umask. */
PyModule_AddIntConstant(module, "MS_UNBINDABLE", MS_UNBINDABLE); /* Change to unbindable. */
PyModule_AddIntConstant(module, "MS_PRIVATE", MS_PRIVATE); /* Change to private. */
PyModule_AddIntConstant(module, "MS_SLAVE", MS_SLAVE); /* Change to slave. */
PyModule_AddIntConstant(module, "MS_SHARED", MS_SHARED); /* Change to shared. */
PyModule_AddIntConstant(module, "MS_RELATIME", MS_RELATIME); /* Update atime relative to mtime/ctime. */
PyModule_AddIntConstant(module, "MS_KERNMOUNT", MS_KERNMOUNT); /* This is a kern_mount call. */
PyModule_AddIntConstant(module, "MS_I_VERSION", MS_I_VERSION); /* Update inode I_version field. */
PyModule_AddIntConstant(module, "MS_STRICTATIME", MS_STRICTATIME); /* Always perform atime updates. */
PyModule_AddIntConstant(module, "MS_ACTIVE", MS_ACTIVE);
PyModule_AddIntConstant(module, "MS_NOUSER", MS_NOUSER);
PyModule_AddIntConstant(module, "MNT_DETACH", MNT_DETACH); /* Just detach from the tree. */
PyModule_AddIntConstant(module, "MS_MGC_VAL", MS_MGC_VAL);
return module;
}