Commit 26de4e95 authored by Kirill Smelkov's avatar Kirill Smelkov

Leverage newuidmap/newgidmap if they are available

Else for regular user we are doomed to always have only single

	X -> UID-in-parent

mapping.

See details in comments inside.

TODO handle case when we have CAP_SETUID already (slapns inside slapns).
parent 337dbc6f
...@@ -21,7 +21,9 @@ ...@@ -21,7 +21,9 @@
from unshare import unshare, CLONE_NEWUSER, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWNET from unshare import unshare, CLONE_NEWUSER, CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWNET
from os.path import abspath from os.path import abspath
from subprocess import call
import os, sys, ctypes, errno import os, sys, ctypes, errno
import logging as log
MS_RDONLY = 1 << 0 MS_RDONLY = 1 << 0
MS_BIND = 1 << 12 MS_BIND = 1 << 12
...@@ -41,6 +43,12 @@ def bind(source, target, flags=0, options=''): ...@@ -41,6 +43,12 @@ def bind(source, target, flags=0, options=''):
return mount(source, target, None, flags | MS_BIND, options) return mount(source, target, None, flags | MS_BIND, options)
# readfile reads data from file @path.
def readfile(path):
with open(path, "rb") as f:
return f.read()
# writefile writes data to file @path. # writefile writes data to file @path.
def writefile(path, data): def writefile(path, data):
with open(path, "wb") as f: with open(path, "wb") as f:
...@@ -56,6 +64,72 @@ def mkdir_p(path, mode=0777): ...@@ -56,6 +64,72 @@ def mkdir_p(path, mode=0777):
else: else:
raise raise
# ---- uid/gid map setup ----
# On Linux security for setting UID translation from a user namespace to its
# parent is very ad-hoc. Basically single `0 -> UID-in-parent` rule is allowed
# to be setup from a regular users, while everything else requires CAP_SETUID
# in parent user namespace:
#
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/user_namespace.c?id=v4.19-rc6-177-gcec4de302c5f#n1086
#
# then there is also newuidmap/newgidmap SUID helpers (part of shadow package) which
# read /etc/sub{uid,gid} to see which ID ranges are allowed for a user.
#
# The defaults, when adding a user into a system, is to populate that file with
# 65K ID ranges for every user, for example
#
# slapuser4:1214112:65536
#
# This way when slapns starts without having CAP_SETUID it tries to use
# newuidmap/newgidmap to setup allowed UID ranges in created namespace.
# subid_allowed finds out which sub uid/gid ranges are allowed for current user.
# -> [] of (id, count)
def subid_allowed(subid_file):
try:
f = open(subid_file, "r")
except IOError as e:
log.warn("%s: cannot find allowed: %s" % (subid_file, e))
return []
mylogin = os.getlogin()
myuid = "%d" % os.getuid()
subidv = []
try:
for _ in f.readlines():
# kirr:1279648:65536
u, id_, count = _.split(":")
if not (u == mylogin or u == myuid):
continue
id_ = int(id_)
count = int(count)
subidv.append((id_, count))
return subidv
finally:
f.close()
# idmap_trysetup_viashadow tries to setup uid/gid mapping for a process
# identified by pid via SUID helpers from shadow package.
def idmap_trysetup_viashadow(kind, pid):
myid = getattr(os, "get"+kind)()
argv = ["new%smap" % kind, "%d" % pid, "0", "%d" % myid, "1"] # includes 0 <- uid which is always allowed
x = 100
for u, count in subid_allowed("/etc/sub%s" % kind):
argv += ["%d" % x, "%d" % u, "%d" % count]
x = x + count
st = call(argv)
if st != 0:
log.warn("new%smap failed -> will fallback to `unshare -r` style setup ..." % kind)
# ----------------------------------------
# usage: slapns <slappart> # usage: slapns <slappart>
# create new container inside <slappart> directory + run it. # create new container inside <slappart> directory + run it.
def main(): def main():
...@@ -71,12 +145,55 @@ def main(): ...@@ -71,12 +145,55 @@ def main():
uid = os.getuid() uid = os.getuid()
gid = os.getgid() gid = os.getgid()
# fork a child in which we will call unshare and onto which we'll apply new{uid,gid}map
unshared_rx, unshared_tx = os.pipe() # parent <- child "unshared"
mapsetup_rx, mapsetup_tx = os.pipe() # parent -> child "uid/gid map setup complete"
pid = os.fork()
if pid != 0:
# parent
os.close(unshared_tx)
os.close(mapsetup_rx)
# <- child "unshared"
_ = os.read(unshared_rx, 1)
assert _ == "U", `_`
# TODO check if we have CAP_SETUID and then we can write /proc/.../uid_map directly
# ^^^ = "slapns inside slapns" case.
# try to setup uid/git mappings via SUID helper
idmap_trysetup_viashadow("uid", pid)
idmap_trysetup_viashadow("gid", pid)
# signal to child {uid,gid}_map setup complete
_ = os.write(mapsetup_tx, "M")
assert _ == 1, _
# wait on child
_, st = os.waitpid(pid, 0)
sys.exit(st >> 8) # st = (exit << 8) | signal
# child
os.close(unshared_rx)
os.close(mapsetup_tx)
# unshare from parent namespace (unshare -Umpn) # unshare from parent namespace (unshare -Umpn)
unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWNET) unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWNET)
# make us a root (UID=0) inside it (unshare -r) # parent <- "unshared"
writefile("/proc/self/setgroups", "deny") _ = os.write(unshared_tx, "U")
assert _ == 1, _
# <- parent "uid/gid map setup complete"
_ = os.read(mapsetup_rx, 1)
assert _ == "M", `_`
# update uid/gid maps trivially (uid/gid 0 inside = `unshare -r`) if parent failed.
if readfile("/proc/self/uid_map") == "":
writefile("/proc/self/uid_map", "0 %d 1" % uid) writefile("/proc/self/uid_map", "0 %d 1" % uid)
if readfile("/proc/self/gid_map") == "":
writefile("/proc/self/setgroups", "deny")
writefile("/proc/self/gid_map", "0 %d 1" % gid) writefile("/proc/self/gid_map", "0 %d 1" % gid)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment