Skip to content

Commit

Permalink
Keep capabilities feature added
Browse files Browse the repository at this point in the history
Example fix
  • Loading branch information
baskiton committed Jan 28, 2022
1 parent a316a07 commit d0f6a77
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 9 deletions.
4 changes: 2 additions & 2 deletions examples/mountpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def foo(ns: pysetns.Namespace):


def bar(pid, namespaces):
ns = pysetns.Namespace(pid, namespaces)
ns.enter(foo, args=(ns,))
ns = pysetns.Namespace(pid, namespaces, keep_caps=True)
ns.enter(foo, ns)
if ns.errors:
print(f'NS errors: {ns.errors}')
return ns.retry
Expand Down
21 changes: 15 additions & 6 deletions pysetns/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from . import ext

__version__ = '0.1.4'
__version__ = '0.2.0'

NS_TIME = ext.CLONE_NEWTIME # time namespace (since Linux 5.8)
NS_MNT = ext.CLONE_NEWNS # mount namespace group (since Linux 3.8)
Expand Down Expand Up @@ -51,7 +51,8 @@ def __init__(self, gid: int, uid: int, pid: Union[int, str]):

class Namespace:
def __init__(self, target_pid: Union[int, str], ns_types: int = NS_ALL,
target_gid: int = 0, target_uid: int = 0, do_fork: int = False, true_user: int = False):
target_gid: int = 0, target_uid: int = 0, do_fork: int = False, true_user: int = False,
keep_caps: bool = False):
if (isinstance(target_pid, int) or target_pid.isdigit()) and int(target_pid) <= 0:
raise ValueError('Invalid target PID')
if not (NS_ALL & ns_types):
Expand All @@ -64,6 +65,7 @@ def __init__(self, target_pid: Union[int, str], ns_types: int = NS_ALL,
self.target_gid = target_gid or proc_st.st_gid
self.target_uid = target_uid or proc_st.st_uid
self.true_user = true_user
self.keep_caps = keep_caps

self.parent_ns_files = {nstype: self._get_nsfd('self', name)
for nstype, name in _NS_NAMES.items()
Expand All @@ -83,8 +85,8 @@ def __init__(self, target_pid: Union[int, str], ns_types: int = NS_ALL,
self.fork = -1

def enter(self, target: Callable, *args, **kwargs) -> None:
if not isinstance(target, Callable):
raise TypeError('target is not callable')
if not callable(target):
raise TypeError('`target` is not callable')

for ns, fd in self.target_ns_files.copy().items():
if ns == NS_USER and not self.true_user:
Expand Down Expand Up @@ -112,8 +114,15 @@ def enter(self, target: Callable, *args, **kwargs) -> None:
self.exit(0)
return
elif self.namespaces & NS_USER:
os.setgid(self.target_gid)
os.setuid(self.target_uid)
if self.keep_caps:
try:
ext.setguid_keep_cap(self.target_gid, self.target_uid)
except OSError as e:
self.errors[NS_USER] = e
self.exit(e.errno)
else:
os.setgid(self.target_gid)
os.setuid(self.target_uid)
exitcode = 0
try:
exitcode = target(*args, **kwargs)
Expand Down
1 change: 1 addition & 0 deletions pysetns/ext.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ CLONE_NEWPID : int
CLONE_NEWNET : int

def setns(fd: int, nstype: int = 0) -> None: ...
def setguid_keep_cap(gid: int, uid: int) -> None: ...
61 changes: 60 additions & 1 deletion src/ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
#include <Python.h>
#include <sched.h>
#include <linux/sched.h>
#include <linux/capability.h>
#include <sys/prctl.h>
#include <syscall.h>


PyDoc_STRVAR(ext__doc__,
Expand Down Expand Up @@ -31,7 +34,7 @@ PySetns(PyObject *self, PyObject *args, PyObject *kwargs)
int fd = -1;
int nstype = 0;

if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|i", kwlist,
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|i:setns", kwlist,
&fd, &nstype)) {
return NULL;
}
Expand All @@ -43,8 +46,64 @@ PySetns(PyObject *self, PyObject *args, PyObject *kwargs)
Py_RETURN_NONE;
}


static long
capget(cap_user_header_t header, cap_user_data_t dataptr)
{
return syscall(SYS_capget, header, dataptr);
}

static long
capset(cap_user_header_t header, cap_user_data_t dataptr)
{
return syscall(SYS_capset, header, dataptr);
}

typedef struct cap_s {
struct __user_cap_header_struct hdr;
struct __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3];
} cap_t;

PyDoc_STRVAR(setguid_keep_cap__doc__,
"setguid_keep_cap(gid: int, uid: int) -> None\n"
"\n"
"Set GID and UID with keep capabilities of the caller.\n"
"\n"
"Args:\n"
" gid (int): Group ID\n"
" uid (int): User ID\n"
"\n"
"Raises:\n"
" OSError: Raised when error occured.\n");

static PyObject *
PySetguid_keep_cap(PyObject *self, PyObject *a, PyObject *kw)
{
static char *kwlist[] = {"gid", "uid", 0};
unsigned int gid, uid;

if (!PyArg_ParseTupleAndKeywords(a, kw, "II:setguid_keep_cap", kwlist, &gid, &uid))
return 0;

cap_t cap = {
.hdr.pid = getpid(),
.hdr.version = _LINUX_CAPABILITY_VERSION_3,
};

if (prctl(PR_SET_KEEPCAPS, 1)
|| capget(&cap.hdr, cap.data)
|| setgid(gid)
|| setuid(uid)
|| capset(&cap.hdr, cap.data))
return PyErr_SetFromErrno(PyExc_OSError);

Py_RETURN_NONE;
}


static PyMethodDef ext_methods[] = {
{"setns", (PyCFunction)PySetns, METH_VARARGS | METH_KEYWORDS, setns__doc__},
{"setguid_keep_cap", (PyCFunction)PySetguid_keep_cap, METH_VARARGS | METH_KEYWORDS, setguid_keep_cap__doc__},
{NULL, NULL, 0, NULL}
};

Expand Down

0 comments on commit d0f6a77

Please sign in to comment.