Skip to content

Commit

Permalink
Bug 1899515 - Inform users of maybe missing AppArmor rules
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandre Lissy committed Jun 7, 2024
1 parent 3e46da4 commit 39477e5
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
6 changes: 6 additions & 0 deletions mozregression/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,12 @@ def create_parser(defaults):

parser.add_argument("--debug", "-d", action="store_true", help="Show the debug output.")

parser.add_argument(
"--dont-check-userns",
action="store_true",
help="Dont check for unprivileged user namespaces AppArmor blocking.",
)

return parser


Expand Down
84 changes: 84 additions & 0 deletions mozregression/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,81 @@ def check_mozregression_version():
)


def check_unprivileged_userns():
"""
Some distribution started to block unprivileged user namespaces via
AppArmor. This might result in crashes on older builds, and in degraded
sandbox behavior. It is fixed with an AppArmor profile that allows the
syscall to proceed, but this is path dependant on the binary we download
and needs to be installed at a system level, so we can only advise people
of the situation.
The following sys entry should be enough to verify whether it is blocked or
not, but the Ubuntu security team recommend cross-checking with actual
syscall. This code is a simplification of how Firerox does it, cf
https://searchfox.org/mozilla-central/rev/23efe2c8c5b3a3182d449211ff9036fb34fe0219/security/sandbox/linux/SandboxInfo.cpp#114-175
and has been the most reliable way so far (shell with unshare would not
reproduce EPERM like we want).
"""

apparmor_file = "/proc/sys/kernel/apparmor_restrict_unprivileged_userns"
if not os.path.isfile(apparmor_file):
return

with open(apparmor_file, "r") as f:
if f.read().strip() != "1":
return

import ctypes
import errno
import signal

# TODO: read from system?
# <x86_64-linux-gnu/asm/unistd_64.h>
SYS_clone = 56

libc = ctypes.CDLL(None, use_errno=True)

LOG.warning(
"Unprivileged user namespaces might be disabled. Checking clone() + unshare() syscalls ..."
)

pid = libc.syscall(SYS_clone, signal.SIGCHLD.value | os.CLONE_NEWUSER, None, None, None, None)
print("pid", pid)

if pid == 0:
# Child side ...
rv = libc.unshare(os.CLONE_NEWPID)
_errno = ctypes.get_errno()
print("rv=", rv)
if rv < 0:
print("rv", rv, "errno", _errno)
sys.exit(_errno)
sys.exit(0)
else:
print("waiting", pid)
(pid, statuscode) = os.waitpid(pid, 0)
exitcode = os.waitstatus_to_exitcode(statuscode)
print("waited", pid, "finshed with", statuscode, " => ", exitcode)

if exitcode == 0:
return

if exitcode == errno.EPERM:
LOG.warning(
"Unprivileged user namespaces are disabled. This is likely because AppArmor policy "
"change. Please refer to XXX to learn how to setup AppArmor so that MozRegression "
"works correctly. Missing AppArmor profile can lead to crashes or to incorrectly "
"sandboxed processes."
)
return

LOG.warning(
"Unexpected exit code {} while performing user namespace "
"checks. You might want to file a bug.".format(exitcode)
)


def main(
argv=None,
namespace=None,
Expand All @@ -327,6 +402,15 @@ def main(
if check_new_version:
check_mozregression_version()
config.validate()
if (
sys.platform
in (
"linux",
"linux2",
)
and not config.options.dont_check_userns
):
check_unprivileged_userns()
set_http_session(get_defaults={"timeout": config.options.http_timeout})

app = Application(config.fetch_config, config.options)
Expand Down

0 comments on commit 39477e5

Please sign in to comment.