Skip to content

Commit

Permalink
test_signal_crashes: replace the use of ping with a custom binary
Browse files Browse the repository at this point in the history
The tests were previously using `ping` under the assumption that it was
setuid or setcap. However, Debian has dropped all of those in
3:20240905-1 since those privileges aren't necessary anymore on recent
kernels.
  • Loading branch information
schopin-pro committed Nov 22, 2024
1 parent e606d88 commit bb9eeb6
Showing 1 changed file with 55 additions and 33 deletions.
88 changes: 55 additions & 33 deletions tests/integration/test_signal_crashes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,41 @@
apport_binary = import_module_from_file(APPORT_PATH)


@contextlib.contextmanager
def create_dropsuid() -> Iterator[str]:
"""Compiles a suid binary that immediately drops privilege then sleeps."""
DROPSUID_SOURCE = """
#include <unistd.h>
int main() {
int euid = geteuid();
int uid = getuid();
// We need to be suid
if (uid == euid)
return 1;
// This call is supposed to succeed?!
if (seteuid(uid))
return 2;
// We actually check that it succeeded.
if (geteuid() != uid)
return 3;
sleep(60);
return 0;
}
"""
with tempfile.TemporaryDirectory() as tempdir:
source = f"{tempdir}/dropsuid.c"
binary = f"{tempdir}/dropsuid"
with open(source, "w", encoding="utf8") as source_fd:
source_fd.write(DROPSUID_SOURCE)
source_fd.flush()
cmd = ["gcc", "-g", source, "-o", binary]
subprocess.run(cmd, check=True)
os.chmod(binary, 0o4755)
yield binary


@contextlib.contextmanager
def create_suid(tmpdir: str = "/var/tmp") -> Iterator[str]:
"""Creates a `sleep` suid binary in a subdirectory of `tmpdir`."""
Expand Down Expand Up @@ -704,32 +739,23 @@ def test_crash_setuid_keep(self) -> None:
# run test program in /run (which should only be writable to root)
self.do_crash(command=suid, uid=MAIL_UID, suid_dumpable=2, cwd="/run")

@unittest.skipUnless(os.path.exists("/bin/ping"), "this test needs /bin/ping")
@unittest.skipIf(os.geteuid() != 0, "this test needs to be run as root")
def test_crash_suid_dumpable_debug(self) -> None:
"""Report generation for setuid program with suid_dumpable set to 1.
ping has cap_net_raw=ep and therefore do_crash needs root.
"""
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))

"""Report generation for setuid program with suid_dumpable set to 1."""
# if a user can crash a suid root binary, it should not create
# core files if /proc/sys/fs/suid_dumpable is set to 1 ("debug")
self.do_crash(
command="/bin/ping", args=["127.0.0.1"], uid=MAIL_UID, suid_dumpable=1
)
with create_suid() as suid:
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
self.do_crash(command=suid, uid=MAIL_UID, suid_dumpable=1)

@unittest.skipUnless(os.path.exists("/bin/ping"), "this test needs /bin/ping")
@unittest.skipIf(os.geteuid() != 0, "this test needs to be run as root")
def test_crash_setuid_drop(self) -> None:
"""Report generation for setuid program which drops root."""
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))

# if a user can crash a suid root binary, it should not create
# core files
self.do_crash(
command="/bin/ping", args=["127.0.0.1"], uid=MAIL_UID, suid_dumpable=2
)
with create_dropsuid() as dropsuid:
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
# if a user can crash a suid root binary, it should not create
# core files
self.do_crash(command=dropsuid, uid=MAIL_UID, suid_dumpable=2)

@unittest.skipIf(os.geteuid() != 0, "this test needs to be run as root")
def test_crash_setuid_unpackaged(self) -> None:
Expand Down Expand Up @@ -772,25 +798,21 @@ def test_core_dump_packaged_sigquit_via_socket(self) -> None:
via_socket=True,
)

@unittest.skipUnless(os.path.exists("/bin/ping"), "this test needs /bin/ping")
@unittest.skipIf(os.geteuid() != 0, "this test needs to be run as root")
def test_crash_setuid_drop_via_socket(self):
"""Report generation via socket for setuid program which drops root."""
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
self.do_crash(
command="/bin/ping",
args=["127.0.0.1"],
uid=MAIL_UID,
suid_dumpable=2,
via_socket=True,
)
with create_dropsuid() as dropsuid:
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
self.do_crash(
command=dropsuid, uid=MAIL_UID, suid_dumpable=2, via_socket=True
)

# check crash report
report = apport.Report()
with open(self.test_report, "rb") as report_file:
report.load(report_file)
self.assertEqual(report["Signal"], "11")
self.assertEqual(report["ExecutablePath"], os.path.realpath("/bin/ping"))
# check crash report
report = apport.Report()
with open(self.test_report, "rb") as report_file:
report.load(report_file)
self.assertEqual(report["Signal"], "11")
self.assertEqual(report["ExecutablePath"], dropsuid)

@unittest.mock.patch("os.readlink")
def test_is_not_same_ns(self, readlink_mock: MagicMock) -> None:
Expand Down

0 comments on commit bb9eeb6

Please sign in to comment.