Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- ubuntu:plucky
container:
image: ${{ matrix.container }}
options: --cap-add=SYS_PTRACE --security-opt seccomp=unconfined
options: --privileged
steps:
- name: Sanitize container name (for artifact name)
run: |
Expand Down
20 changes: 20 additions & 0 deletions data/apport
Original file line number Diff line number Diff line change
Expand Up @@ -503,26 +503,46 @@

def is_same_ns(proc_pid: ProcPid, ns: str) -> bool:
if not os.path.exists(f"/proc/self/ns/{ns}") or not proc_pid.exists(f"ns/{ns}"):
print(f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): does not exist (True 1)")

Check warning on line 506 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L506

Added line #L506 was not covered by tests
# If the namespace doesn't exist, then it's obviously shared
return True

try:
path = proc_pid.readlink(f"ns/{ns}")
print(

Check warning on line 512 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L511-L512

Added lines #L511 - L512 were not covered by tests
f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): "
f"proc_pid.readlink('ns/{ns}') = {path}"
)
path = os.readlink(f"/proc/self/ns/{ns}")
print(

Check warning on line 517 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L516-L517

Added lines #L516 - L517 were not covered by tests
f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): "
f"os.readlink('/proc/self/ns/{ns}') = {path}"
)
if proc_pid.readlink(f"ns/{ns}") == os.readlink(f"/proc/self/ns/{ns}"):
# Check that the inode for both namespaces is the same
print(f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): same ns (True 2)")

Check warning on line 523 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L523

Added line #L523 was not covered by tests
return True
except OSError as error:
print(f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): OSError: {error}")

Check warning on line 526 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L526

Added line #L526 was not covered by tests
if error.errno == errno.ENOENT:
print(f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): ENOENT (True 2)")

Check warning on line 528 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L528

Added line #L528 was not covered by tests
return True
raise

# check to see if the process is part of the system.slice (LP: #1870060)
with contextlib.suppress(FileNotFoundError):
with proc_pid.open("cgroup") as cgroup:
for line in cgroup:
print(f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): cgroup line: {line}")

Check warning on line 536 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L536

Added line #L536 was not covered by tests
fields = line.split(":")
if fields[-1].startswith("/system.slice"):
print(

Check warning on line 539 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L539

Added line #L539 was not covered by tests
f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}):"
f" system.slice (True 4)"
)
return True

print(f"DEBUG: is_same_ns({proc_pid.pid}, {ns!r}): False")

Check warning on line 545 in data/apport

View check run for this annotation

Codecov / codecov/patch

data/apport#L545

Added line #L545 was not covered by tests
return False


Expand Down
70 changes: 66 additions & 4 deletions tests/integration/test_signal_crashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,34 @@
}
"""

UNSHARE_CODE = """\

Check warning on line 93 in tests/integration/test_signal_crashes.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_signal_crashes.py#L93

Added line #L93 was not covered by tests
#define _GNU_SOURCE
#include <err.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
if (unshare(CLONE_NEWNS | CLONE_NEWPID) == -1)
err(EXIT_FAILURE, "unshare");

pid_t child = fork();
if (child < 0) {
err(EXIT_FAILURE, "fork");
} else if (child > 0) {
int rc;
waitpid(child, &rc, 0);
return rc;
}

int zero = 0;
printf("42 / 0 = %i\\n", 42 / zero);
return 0;
}
"""


@contextlib.contextmanager
def compile_c_code(name: str, c_code: str, tmpdir: str = "/var/tmp") -> Iterator[str]:
Expand Down Expand Up @@ -785,6 +813,30 @@
suid_dumpable=2,
)

@unittest.skipIf(os.geteuid() != 0, "this test needs to be run as root")

Check warning on line 816 in tests/integration/test_signal_crashes.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_signal_crashes.py#L816

Added line #L816 was not covered by tests
def test_crash_unshare(self) -> None:
"""Report generation for a binary that uses unshare and crashes.

This unshare test case simulates a container crash that does
not have Apport support.
"""
with (
self.assertLogs(level="WARNING") as warning_logs,
compile_c_code("unshare", UNSHARE_CODE) as unshare,
):
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
self.do_crash(

Check warning on line 828 in tests/integration/test_signal_crashes.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_signal_crashes.py#L827-L828

Added lines #L827 - L828 were not covered by tests
command=unshare,
command_dies=True,
follow_fork=True,
expect_report=False,
)

self.assertRegex(

Check warning on line 835 in tests/integration/test_signal_crashes.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_signal_crashes.py#L835

Added line #L835 was not covered by tests
warning_logs.output[0],
r"ERROR:root:host pid \d+ crashed in a container without apport support",
)

@unittest.skipIf(os.geteuid() != 0, "this test needs to be run as root")
def test_crash_setuid_unpackaged(self) -> None:
"""Report generation for unpackaged setuid program."""
Expand Down Expand Up @@ -1177,6 +1229,8 @@
via_socket: bool = False,
cwd: str | None = None,
expected_owner: int | None = None,
command_dies: bool = False,
follow_fork: bool = False,
**kwargs: typing.Any,
) -> str:
# TODO: Split into smaller functions/methods
Expand Down Expand Up @@ -1228,7 +1282,7 @@

try:
gdb = subprocess.Popen( # pylint: disable=consider-using-with
self.gdb_command(command, args, gdb_core_file, uid),
self.gdb_command(command, args, gdb_core_file, uid, follow_fork),
env={"HOME": self.workdir},
stdin=subprocess.PIPE,
cwd=cwd,
Expand All @@ -1243,10 +1297,13 @@

try:
command_process = self.wait_for_gdb_child_process(
gdb.pid, expected_command or command
gdb.pid,
expected_command or command,
"tracing-stop" if command_dies else "sleeping",
)

os.kill(command_process.pid, sig)
if not command_dies:
os.kill(command_process.pid, sig)

Check warning on line 1306 in tests/integration/test_signal_crashes.py

View check run for this annotation

Codecov / codecov/patch

tests/integration/test_signal_crashes.py#L1306

Added line #L1306 was not covered by tests
self.wait_for_core_file(gdb.pid, gdb_core_file)

if hook_before_apport:
Expand Down Expand Up @@ -1304,7 +1361,11 @@

@staticmethod
def gdb_command(
command: str, args: Iterable[str], core_file: str, uid: int | None
command: str,
args: Iterable[str],
core_file: str,
uid: int | None,
follow_fork: bool = False,
) -> list[str]:
"""Construct GDB arguments to call the test executable.

Expand All @@ -1325,6 +1386,7 @@
f" --reuid={uid} --clear-groups /bin/sh -c 'exec {command}{cmd_args}'"
)
command = "/usr/bin/setpriv"
if uid is not None or follow_fork:
gdb_args += ["--ex", "set follow-fork-mode child"]

gdb_args += [
Expand Down
Loading