Skip to content
Open
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
13 changes: 12 additions & 1 deletion apparmor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,12 @@ disagreements, each where the package is stricter (`/a rw -> b`, `foo r,`,
`uX`, `profile -e`, the unix, pivot_root, rlimit and link rules outside the
model, `abi` without the system's abi files, and change_profile safe and
unsafe on disjoint exec conditions). A disagreement outside the list fails
the test. Runtime decisions could not be compared with the kernel: loading
the test. Generated profiles whose file rules are all audited denials also
receive a separate, modelled read grant: Ubuntu 24.04 parser 4.0.1 package
revision .9 has a [reported regression](https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/2171436)
affecting that shape. The denial remains tested. Parser failures report the
exit status or signal and complete stdout and stderr; `ref/test_gen.py` checks
the fixture and diagnostics. Runtime decisions could not be compared with the kernel: loading
a profile needs root, and none was used.

Run on the JavaScript lane with 80 policies (2,080 queries) each, seeds
Expand Down Expand Up @@ -524,6 +529,12 @@ of build each).

## Status

Bend 2.0.39 (2026-11-03): `scripts/check.sh apparmor` and
`VERDICT=1 scripts/check.sh apparmor` pass; the reference run accepts 20
policies with Ubuntu 24.04 apparmor_parser 4.0.1 package revision .9,
with 53 edge agreements and 10 known disagreements. `scripts/deps.sh
apparmor` and `python3 scripts/internals.py apparmor` pass.

Bend 2.0.39 (2026-10-26, native, three packages at a time): `PROOF.bend`
(4 s, 0.23 GB peak; `--verdict` 8 s, 0.29 GB peak) ALL PROOFS CHECK, with
and without `--verdict`.
Expand Down
2 changes: 1 addition & 1 deletion apparmor/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0.0
0.1.0.1
23 changes: 19 additions & 4 deletions apparmor/ref/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,20 @@ def fill(p, n):
top2 = Profile('top2', r.pick(['/usr/bin/t*', '/usr/bin/**']), 'enforce', False)
fill(top2, 1)
pol['profiles'].append(top2)
# Some 4.0.1 builds fail compiling profiles whose only file rules are
# audit deny. Keep those rules and add a separate, modelled allow rule.
def ensure_file_allow(p):
files = [x for x in p.rules if x.kind == 'file']
if files and all(x.q['audit'] and x.q['deny'] for x in files):
x = Rule()
x.kind, x.q = 'file', {'audit': False, 'deny': False, 'owner': False}
x.text, x.letters, x.xmode, x.target = '/aa-ref-allow', 'r', None, None
x.src = '/aa-ref-allow r,'
p.rules.append(x)
for child in p.children:
ensure_file_allow(child)
for profile in pol['profiles']:
ensure_file_allow(profile)
return pol

def render_profile(p, depth):
Expand Down Expand Up @@ -1029,9 +1043,9 @@ def parser_accepts(files, main, tmp):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'w') as f:
f.write(text)
res = subprocess.run(['apparmor_parser', '-Q', '-K', '-q', '--base', tmp, '-I', tmp, os.path.join(tmp, main)],
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True)
return res.returncode == 0, res.stderr.strip().split('\n')[-1] if res.stderr else ''
res = subprocess.run(['apparmor_parser', '-Q', '-K', '--base', tmp, '-I', tmp, os.path.join(tmp, main)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
return res.returncode == 0, '%s; stdout: %s; stderr: %s' % (('signal %d' % -res.returncode) if res.returncode < 0 else ('exit code %d' % res.returncode), res.stdout or '(empty)', res.stderr or '(empty)')

def main():
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 2026
Expand Down Expand Up @@ -1116,4 +1130,5 @@ def main():
if parser_bad or bad_edges:
sys.exit(1)

main()
if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions apparmor/ref/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
set -uo pipefail
cd "$(dirname "$0")"
command -v python3 > /dev/null || { echo "python3 missing"; exit 77; }
python3 test_gen.py || exit 1
read -r -a bend <<< "${BEND:-bend}"
n=$(python3 gen.py "${SEED:-2026}" "${POLICIES:-20}") || { printf '%s\n' "$n"; exit 1; }
got=$(for f in cases-*.bend; do "${bend[@]}" "$f" 2>&1; done)
Expand Down
33 changes: 33 additions & 0 deletions apparmor/ref/test_gen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import tempfile
import unittest
from unittest.mock import patch
from types import SimpleNamespace

import gen


class GeneratorTests(unittest.TestCase):
def test_seed_policy_keeps_denial_and_adds_allow(self):
r = gen.Rng(2026)
gen.random.seed(2026)
for i in range(9):
pol = gen.gen_policy(r, i)
names = ['main', 'main//c1', 'other'] + (
['main//h1'] if any(c.hat for c in pol['profiles'][0].children) else [])
gen.gen_queries(r, pol, names)
rules = pol['profiles'][1].rules
self.assertTrue(any(x.kind == 'file' and x.q['audit'] and x.q['deny'] for x in rules))
self.assertTrue(any(x.kind == 'file' and not x.q['deny'] for x in rules))

def test_parser_diagnostics_include_signal_and_all_output(self):
result = SimpleNamespace(returncode=-11, stdout='first\nsecond\n', stderr='one\ntwo\n')
with tempfile.TemporaryDirectory() as tmp, patch('gen.subprocess.run', return_value=result):
ok, message = gen.parser_accepts({'main': ''}, 'main', tmp)
self.assertFalse(ok)
self.assertIn('signal 11', message)
self.assertIn('first\nsecond\n', message)
self.assertIn('one\ntwo\n', message)


if __name__ == '__main__':
unittest.main()
Loading