Commit a9af3a8a authored by Kirill Smelkov's avatar Kirill Smelkov

trun: Deactivate most capabilities before spawning user test

In the previous patch we asked unshare to keep capabilities so that FUSE
mounting works from under regular user. However full set of capabilities
is too much, and in particular if cap_dac_override is present(*), it means
that writes to files that have read-only permission, is not rejected by
kernel.

-> Adjust trun to retain only those capabilities that we actually need
   = CAP_SYS_ADMIN to mount things.

This should fix the following Go build failure:

    --- FAIL: TestReadOnlyWriteFile (0.00s)
        ioutil_test.go:90: Expected an error when writing to read-only file /tmp/TestReadOnlyWriteFile3940340549/blurp.txt
    FAIL
    FAIL	io/ioutil	0.053s

P.S. And if we would unshare to root instead (unshare -Umr) it should be
still a good idea to drop extra capabilities, as we still want to reject
writes to read-only files.

(*) see https://man7.org/linux/man-pages/man7/capabilities.7.html

/helped-and-reviewed-by @jerome
/reviewed-on !13
parent a191468f
...@@ -259,3 +259,18 @@ TestCase('TESTCASE', ['mount', '-t', 'tmpfs', 'none', '/etc']) ...@@ -259,3 +259,18 @@ TestCase('TESTCASE', ['mount', '-t', 'tmpfs', 'none', '/etc'])
""") """)
captured = capsys.readouterr() captured = capsys.readouterr()
assert "# leaked mount: none /etc tmpfs" in captured.out assert "# leaked mount: none /etc tmpfs" in captured.out
# verify that inside environment, that nxdtest creates, file permissions are
# still respected.
def test_run_writero(run_nxdtest, capsys):
twritero = "%s/testprog/twritero" % (dirname(__file__),)
run_nxdtest(
"""\
TestCase('TESTNAME', ['%s'])
""" % twritero)
captured = capsys.readouterr()
output_lines = captured.out.splitlines()
assert re.match(u"# ran 1 test case: 1·ok", output_lines[-1])
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2021 Nexedi SA and Contributors.
#
# This program is free software: you can Use, Study, Modify and Redistribute
# it under the terms of the GNU General Public License version 3, or (at your
# option) any later version, as published by the Free Software Foundation.
#
# You can also Link and Combine this program with other software covered by
# the terms of any of the Free Software licenses or any of the Open Source
# Initiative approved licenses and Convey the resulting work. Corresponding
# source of such a combination shall include the source code for all other
# software used.
#
# This program is distributed WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options.
"""Program twritero helps to verify that nxdtest runs tests in sane environment.
It verifies that write to a file with readonly permission is rejected.
"""
from __future__ import print_function, absolute_import
import os, tempfile
from errno import EACCES
from golang import func, defer
@func
def main():
fd, path = tempfile.mkstemp(prefix='twritero.')
def _():
os.remove(path)
defer(_)
os.close(fd)
os.chmod(path, 0o444)
try:
with open(path, "w+") as f:
f.write("zzz")
except IOError as e:
if e.errno != EACCES:
raise
print("write to r/o file rejected ok")
else:
raise AssertionError("write to r/o file not rejected")
if __name__ == '__main__':
main()
...@@ -34,6 +34,7 @@ import errno, os, sys, stat, difflib ...@@ -34,6 +34,7 @@ import errno, os, sys, stat, difflib
from subprocess import check_call as xrun, CalledProcessError from subprocess import check_call as xrun, CalledProcessError
from os.path import join, devnull from os.path import join, devnull
from golang import func, defer from golang import func, defer
import prctl
def main(): def main():
# Try to respawn ourselves in user-namespace where we can mount things, e.g. new /tmp. # Try to respawn ourselves in user-namespace where we can mount things, e.g. new /tmp.
...@@ -81,6 +82,11 @@ def main(): ...@@ -81,6 +82,11 @@ def main():
# run_in_userns runs f with checks assuming that we are in a user namespace. # run_in_userns runs f with checks assuming that we are in a user namespace.
@func @func
def run_in_userns(f): def run_in_userns(f):
# leave only capabilities that are needed for mount/fusermount.
# in particular drop cap_dac_override so that file permissions are still
# respected (e.g. write to read/only file is rejected).
prctl.cap_inheritable.limit('sys_admin')
# mount new /tmp and /dev/shm to isolate this run from other programs and to detect # mount new /tmp and /dev/shm to isolate this run from other programs and to detect
# leaked temporary files at the end. # leaked temporary files at the end.
tmpreg = { tmpreg = {
......
...@@ -13,7 +13,7 @@ setup( ...@@ -13,7 +13,7 @@ setup(
keywords = 'Nexedi testing infrastructure tool tox', keywords = 'Nexedi testing infrastructure tool tox',
packages = find_packages(), packages = find_packages(),
install_requires = ['erp5.util', 'six', 'pygolang', 'psutil'], install_requires = ['erp5.util', 'six', 'pygolang', 'psutil', 'python-prctl'],
extras_require = { extras_require = {
'test': ['pytest', 'pytest-timeout', 'setproctitle'], 'test': ['pytest', 'pytest-timeout', 'setproctitle'],
}, },
......
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