Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-enables paxtest in testinfra #5848

Merged
merged 1 commit into from
Mar 31, 2021
Merged
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
18 changes: 18 additions & 0 deletions molecule/testinfra/common/paxtest_results.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Executable anonymous mapping : Killed
Executable bss : Killed
Executable data : Killed
Executable heap : Killed
Executable stack : Killed
Executable shared library bss : Killed
Executable shared library data : Killed
Executable anonymous mapping (mprotect) : Killed
Executable bss (mprotect) : Killed
Executable data (mprotect) : Killed
Executable heap (mprotect) : Killed
Executable stack (mprotect) : Killed
Executable shared library bss (mprotect) : Killed
Executable shared library data (mprotect): Killed
Return to function (strcpy) : paxtest: return address contains a NULL byte.
Return to function (memcpy) : {{ memcpy_result }}
Return to function (strcpy, PIE) : paxtest: return address contains a NULL byte.
Return to function (memcpy, PIE) : {{ memcpy_result }}
74 changes: 43 additions & 31 deletions molecule/testinfra/common/test_grsecurity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import pytest
import re
import warnings
import io
import difflib
import os
from jinja2 import Template

import testutils

Expand Down Expand Up @@ -99,39 +103,47 @@ def test_grsecurity_sysctl_options(host, sysctl_opt):
assert host.sysctl(sysctl_opt[0]) == sysctl_opt[1]


@pytest.mark.skip_in_prod
@pytest.mark.parametrize('paxtest_check', [
"Executable anonymous mapping",
"Executable bss",
"Executable data",
"Executable heap",
"Executable stack",
"Executable shared library bss",
"Executable shared library data",
"Executable anonymous mapping (mprotect)",
"Executable bss (mprotect)",
"Executable data (mprotect)",
"Executable heap (mprotect)",
"Executable stack (mprotect)",
"Executable shared library bss (mprotect)",
"Executable shared library data (mprotect)",
"Writable text segments",
"Return to function (memcpy)",
"Return to function (memcpy, PIE)",
])
def test_grsecurity_paxtest(host, paxtest_check):
def test_grsecurity_paxtest(host):
"""
Check that paxtest does not report anything vulnerable
Requires the package paxtest to be installed.
The paxtest package is currently being installed in the app-test role.
Check that paxtest reports the expected mitigations. These are
"Killed" for most of the checks, with the notable exception of the
memcpy ones. Only newer versions of paxtest will fail the latter,
regardless of kernel.
"""
if host.exists("/usr/bin/paxtest"):
if not host.exists("/usr/bin/paxtest"):
warnings.warn("Installing paxtest to run kernel tests")
with host.sudo():
host.run("apt-get update && apt-get install -y paxtest")
try:
with host.sudo():
# Log to /tmp to avoid cluttering up /root.
paxtest_cmd = "paxtest blackhat /tmp/paxtest.log"
# Select only predictably formatted lines; omit
# the guesses, since the number of bits can vary
paxtest_cmd += " | grep -P '^(Executable|Return)'"
paxtest_results = host.check_output(paxtest_cmd)

paxtest_template_path = "{}/paxtest_results.j2".format(
os.path.dirname(os.path.abspath(__file__)))

memcpy_result = "Killed"
# Versions of paxtest newer than 0.9.12 or so will report
# "Vulnerable" on memcpy tests, see details in
# https://github.com/freedomofpress/securedrop/issues/1039
if host.system_info.codename == "focal":
memcpy_result = "Vulnerable"
with io.open(paxtest_template_path, 'r') as f:
paxtest_template = Template(f.read().rstrip())
paxtest_expected = paxtest_template.render(memcpy_result=memcpy_result)

# The stdout prints here will only be displayed if the test fails
for paxtest_diff in difflib.context_diff(paxtest_expected.split('\n'),
paxtest_results.split('\n')):
print(paxtest_diff)
assert paxtest_results == paxtest_expected
finally:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paxtest isn't removed in this finally clause, it needs sudo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Fixed.

with host.sudo():
c = host.run("paxtest blackhat")
assert c.rc == 0
assert "Vulnerable" not in c.stdout
regex = r"^{}\s*:\sKilled$".format(re.escape(paxtest_check))
assert re.search(regex, c.stdout)
host.run("apt-get remove -y paxtest")


@pytest.mark.skip_in_prod
Expand Down