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 Oct 29, 2024
1 parent f558f7d commit cbffd3c
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 0 deletions.
38 changes: 38 additions & 0 deletions docs/documentation/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,41 @@ to date list of available options.
- List firefox releases numbers

mozregression --list-releases

## Unprivileged user namespaces

AppArmor can be used to restrict the usage of this kernel feature, which is
known to have shipped with at least Ubuntu 24.04 [AppArmor Ubuntu 24.04](https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2046844).

This restricts the ability of the Firefox sandbox and might impair a
mozregression bisection because of the difference of behavior. Also, builds
before [Bug 1884347](https://bugzilla.mozilla.org/show_bug.cgi?id=1884347) will
just crash their content processes.

You can either disable the AppArmor restriction or install an AppArmor profile,
e.g. at `/etc/apparmor.d/firefox-mozregression` with the following content that
will grant the `userns` permission to Firefox binaries under
`/tmp/mozregression/*`:

abi <abi/4.0>,
include <tunables/global>

profile firefox-mozregression /tmp/mozregression/*/firefox/firefox{,-bin} flags=(unconfined) {
userns,

# Site-specific additions and overrides. See local/README for details.
include if exists <local/firefox>
}

Then apply with a

sudo systemctl restart apparmor.service

You can then prefix your `mozregression` calls with a `TMP=` to make use of the
aforthmentionned directory where `userns` is granted:

TMP=/tmp/mozregression/ mozregression [...]

There was a window of time during which Firefox incorrectly tested for that
feature and it would end up in tab crashing. In case of doubt you can always
set environment variable `MOZ_ASSUME_USER_NS=0` before running `mozregression`.
9 changes: 9 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="Do not check for unprivileged user namespaces AppArmor blocking.",
)

return parser


Expand Down Expand Up @@ -515,6 +521,9 @@ def __init__(self, options, config):
)

self.enable_telemetry = config["enable-telemetry"] not in ("no", "false", 0)
self.dont_check_userns = (
config["dont-check-userns"] in ("yes", "true", 1) or self.options.dont_check_userns
)

self.action = None
self.fetch_config = None
Expand Down
1 change: 1 addition & 0 deletions mozregression/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"taskcluster-accesstoken": None,
"taskcluster-clientid": None,
"enable-telemetry": True,
"dont-check-userns": False,
}


Expand Down
122 changes: 122 additions & 0 deletions mozregression/linux_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
Various linux-specific tools
"""

import os
import sys


def check_unprivileged_userns(logger):
"""
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).
Return False if there is no problem or True if the user needs to fix their
setup.
"""

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

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

import ctypes
import errno
import platform
import signal

# Values are from
# https://github.com/hrw/syscalls-table/tree/163e238e4d7761fcf6ac500aad92d53ac88d663a/system_calls/tables
# imported from linux kernel headers
SYS_clone = {
"i386": 120,
"x32": 1073741880,
"x86_64": 56,
"arm": 120,
"armv7l": 120,
"arm64": 220,
"aarch64": 220,
"aarch64_be": 220,
"armv8b": 220,
"armv8l": 220,
}.get(platform.machine())
if not SYS_clone:
logger.warning(
"Unprivileged user namespaces might be disabled, but unsupported platform? {}".format(
platform.machine()
)
)
return False

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

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

try:
# Introduced in 3.12 which is the version of Ubuntu 24.04
clone_newuser = os.CLONE_NEWUSER
clone_newpid = os.CLONE_NEWPID
except AttributeError:
# From
# https://github.com/torvalds/linux/blob/5bbd9b249880dba032bffa002dd9cd12cd5af09c/include/uapi/linux/sched.h#L31-L32
# Last change 12 years ago, so it should be a stable fallback
clone_newuser = 0x10000000
clone_newpid = 0x20000000

pid = libc.syscall(SYS_clone, signal.SIGCHLD.value | clone_newuser, None, None, None, None)

if pid == 0:
# Child side ...
rv = libc.unshare(clone_newpid)
_errno = ctypes.get_errno()
if rv < 0:
sys.exit(_errno)
sys.exit(0)
else:
(pid, statuscode) = os.waitpid(pid, 0)
exitcode = os.waitstatus_to_exitcode(statuscode)

if exitcode == 0:
return False

if exitcode == errno.EPERM:
logger.warning(
"Unprivileged user namespaces is disabled. This is likely because AppArmor policy "
"change. Please refer to {} to learn how to setup AppArmor so that mozregression "
"works correctly. Missing AppArmor profile can lead to crashes or to incorrectly "
"sandboxed processes.".format(
"https://mozilla.github.io/mozregression/documentation/usage.html#unprivileged-user-namespaces" # noqa: E501
)
)
logger.warning(
"If you already applied the suggested fix, then this warning can be ignored. "
"It can also be silenced by the --dont-check-userns flag."
"Another side effect is that browser's tab may crash because "
"they incorrectly test for the feature. If your regression "
"window covers that, you may want to set MOZ_ASSUME_USER_NS=0 "
"environmnent variable before launching mozregression."
)
return True

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

return False
10 changes: 10 additions & 0 deletions mozregression/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from mozregression.fetch_build_info import IntegrationInfoFetcher, NightlyInfoFetcher
from mozregression.json_pushes import JsonPushes
from mozregression.launchers import REGISTRY as APP_REGISTRY
from mozregression.linux_utils import check_unprivileged_userns
from mozregression.network import set_http_session
from mozregression.persist_limit import PersistLimit
from mozregression.telemetry import UsageMetrics, get_system_info, send_telemetry_ping_oop
Expand Down Expand Up @@ -327,6 +328,15 @@ def main(
if check_new_version:
check_mozregression_version()
config.validate()
if (
sys.platform
in (
"linux",
"linux2",
)
and not config.dont_check_userns
):
check_unprivileged_userns(LOG)
set_http_session(get_defaults={"timeout": config.options.http_timeout})

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

0 comments on commit cbffd3c

Please sign in to comment.