Before you start#
Required: Android’s Security Model. Optional: Init & Android services for domain transitions, and Debug, test & contribute for source review. This lab is static/read-only: it uses a synthetic AVC record and a real, pinned AOSP policy excerpt. It does not require a device, SELinux mode changes, or policy compilation. Do not paste rules into a device or use audit2allow as an automatic fix (AOSP’s validation guide notes that audit2allow is no longer provided as part of AOSP).
Learning goals#
Parse a denial into source context, target context, object class, permission and result; distinguish a process domain from UID and global SELinux mode; tell policy source from compiled runtime policy; propose a narrow rule only after checking labels, DAC, existing rules and neverallow assertions; design both positive and negative regression tests. A synthetic example is not a runtime finding.
How to read the evidence
A SELinux access decision concerns a subject (usually a process domain), an object (with an SELinux type), an object class (such as file), and requested permissions (such as read). A common AVC record includes scontext, tcontext, tclass, and denied { ... } (SELinux concepts). The source/target labels are not Linux UIDs. The record may also include PID, command name, path, audit ID, and enforcing/permissive information depending on source and log format.
getenforce reports global mode (SELinux validation); ps -AZ/ls -Z or id -Z can show labels if available and permitted. Source policy is not necessarily the effective device policy: OEM/vendor policy, generated attributes, policy version, boot configuration and runtime changes can differ. Absence of a log line is not evidence of an allow: logs can be filtered, inaccessible, overwritten, or the denial can occur in another layer. A visible AVC is useful evidence, but does not prove DAC or application/service checks passed.
Practice#
Provided synthetic dataset
Synthetic teaching data, not a device capture.
- Subject: scontext
- toy_reader is the process domain, not a package name or UID.
- Object: tcontext
- toy_input_file is the object type, not its pathname.
- Operation: class + permission
- tclass=file and denied { read } identify the requested access.
- Result: permissive=0
- The supplied record describes an enforcing denial; effective policy still needs separate evidence.
The following is invented for this exercise; it is not copied from a device log, does not claim that AOSP Android 14 emits this event, and the sample types are not real AOSP labels:
avc: denied { read } for pid=4242 comm="toy_reader"
path="/data/local/tmp/lab-note"
dev="tmpfs" ino=81
scontext=u:r:toy_reader:s0
tcontext=u:object_r:toy_input_file:s0
tclass=file permissive=0
Interpretation: a process labeled toy_reader attempted a read on an object labeled toy_input_file, whose class is file; the synthetic record says the request was denied while enforcing (permissive=0). It says nothing by itself about the process UID, file owner/mode, whether the path label is correct, whether a framework service was involved, or the complete policy. The comm and path strings are clues, not the policy identities.
Real policy excerpt for comparison
At AOSP Android 14 tag android-14.0.0_r1, private/shell.te contains this actual policy excerpt (pinned source, the rules shown are at lines 18 and 132–133 of that file):
allow shell config_gz:file r_file_perms;
...
allow shell self:perf_event { open read write kernel };
neverallow shell self:perf_event ~{ open read write kernel };
The first rule grants a specific domain/type/class permission set in this policy source. The later allow and neverallow show that policy contains an explicit permitted set and a complementary prohibited set for that example. neverallow is a policy assertion that prohibits a combination; AOSP’s customization guide describes neverallow rules as enforced across devices through compatibility testing. It is not a runtime instruction that repairs an access. AOSP’s SELinux validation guidance describes policy validation; do not infer from this small excerpt that every shell action is allowed or that every vendor compiles this exact policy unchanged.
Reason about a minimal proposal
Suppose this were a real lab, the source and object labels were verified, the process were actually in toy_reader, the request were only file read, DAC allowed it, and no other required rule or constraint applied. The narrow illustrative proposal would be:
allow toy_reader toy_input_file:file read;
This is a reasoning example, not a valid Android policy patch: those types are synthetic, no policy was compiled, and no target device was tested. Do not widen it to allow toy_reader ... over all types or permissions. First ask whether the label is wrong, a supported service/API should be used instead, or data should be placed in a type already intended for this consumer.
Even with a matching SELinux allow, the file open can still fail DAC. For example, if Unix owner/group/mode does not grant the process read access, or a parent directory lacks search permission, the kernel can return EACCES. SELinux permission does not override DAC. Other gates may also fail. Conversely, changing mode bits cannot override a missing SELinux allow in enforcing mode.
Before accepting a proposal, search the complete policy source for matching allow, neverallow, type attributes, macros and domain transitions. A neverallow conflict may make policy compilation fail; it must not be “worked around” by deleting or weakening that assertion without understanding its security purpose. A targeted rule can also violate least privilege even when it compiles. Vendor policy compatibility and generated policy must be checked for the exact build.
Label and policy triage order
When a real, authorized lab denial is available, use a fixed order so an AVC does not become a reflexive request for a new rule:
- Confirm the reproducer, timestamp, serial, build fingerprint, and process identity. A command name alone is not a domain; inspect the process label using a supported read-only tool.
- Confirm the target object and its actual label. A path in a log is not itself the SELinux type; file-context rules, mount labels, generated labeling, or an incorrect path can explain a mismatch. Do not relabel a broad tree to make the error disappear.
- Read the class and exact permission.
file readdiffers fromdir search,open,write,execute, or a Binder call. A path traversal can require directory search even when the final file’s mode allows reading. - Check DAC separately: process UID/GID/groups, owner, mode bits and parent-directory traversal. A missing DAC permission can produce an ordinary refusal without any AVC. A proposed SELinux allow cannot repair it.
- Locate the domain/type in the policy source for the matching platform/vendor revision. Follow macros, type attributes, transitions and existing
neverallowconstraints. Do not treat a short source excerpt as the compiled policy. - State the smallest intended operation and the neighboring operations that must remain denied. Only an owner-maintained policy change with a clear security justification can proceed to compilation and regression review.
This sequence avoids the common “AVC says denied, paste the line” mistake. The denial describes an access that policy refused; it does not tell you whether the design should allow that access. If an app needs one protected operation, a narrow Binder API or correctly labeled data flow may be safer than broad direct file access.
Test plan: both allow and deny
Design tests before changing any policy. For this synthetic example, write the expected outcomes as a test plan only:
| Case | Subject and object | Expected SELinux result if proposal were valid | Other check / interpretation |
|---|---|---|---|
| Positive | toy_reader reads correctly labeled toy_input_file | SELinux permits this requested read | DAC must also permit; test only the intended file, not a directory tree |
| Negative A | toy_reader writes the same object | Denied by SELinux because proposal grants only read | DAC may also deny; isolate layers if the lab environment allows |
| Negative B | Unrelated untrusted_app reads toy_input_file | Denied unless a separate justified rule exists | Confirm no broad attribute rule already grants it |
| Negative C | toy_reader reads an unrelated object type | Denied unless separately granted | Check the entire policy/attributes, not just the one line |
| Negative D | Correct SELinux labels but Unix mode lacks read or parent search | SELinux may allow, but open still fails at DAC | This is the explicit “allow rule yet permission still fails” case |
No policy build or test was run here. On an owned AOSP development tree, a policy change would require normal policy build checks, including neverallow, and behavior tests on the exact image. The test must demonstrate the intended access and the neighboring denials; a successful build alone is not proof of least privilege.
The outcome matrix is intentionally layered. For the positive case, assert both that the right subject and right object are labeled as expected and that only the requested read succeeds. For each negative case, record whether the refusal came from SELinux (with evidence), DAC, or remained unattributed. Avoid creating artificial failures by chmod-ing unrelated files or mutating system policy. A valid negative test should be expected from the policy design, not produced by weakening the environment and then “fixing” it.
Optional read-only device observation
If you already have an owned disposable AOSP emulator, select its serial explicitly and gather only observations:
adb devices -l
adb -s SERIAL shell getenforce
adb -s SERIAL shell id
adb -s SERIAL shell id -Z
adb -s SERIAL shell ps -AZ
adb -s SERIAL logcat -d -b all | grep -i 'avc: denied' | tail -20
Commands and available fields vary; id -Z, process labels, and logs can be restricted. No AVC output means unknown, not “no SELinux denial.” Do not generate a denial by changing security settings, do not set permissive mode, and do not assume a refusal was SELinux without an AVC or another reliable source of evidence. Record exact build and time if logs are present.
Knowledge check#
- In the synthetic record, what are the subject domain, object type, class, and denied permission?
- Does
permissive=0tell you the caller’s UID? - If a narrow SELinux allow is present but Unix mode blocks reading, should the operation succeed?
- What is the purpose of
neverallow? - What does an empty AVC search prove?
Answers#
toy_reader;toy_input_file;file;read.- No. SELinux context and Unix UID are different identity dimensions.
- No. DAC can still deny the open, as can other checks.
- It asserts forbidden policy combinations so that policy checks can flag violations; it is not a runtime grant or a troubleshooting command.
- Nothing conclusive about authorization. Logs may be filtered/unavailable, or failure may occur elsewhere.
Checkpoint#
Submit the synthetic record parsed into fields, one narrowly scoped proposal with assumptions listed, a neverallow/attribute review plan, and one positive plus three negative tests. Ready if you separate evidence from invented data and include DAC failure; revise if you propose permissive mode, broad grants, or copy-pasted audit2allow; not yet evidenced if you claim the synthetic line exists in Android runtime logs. Remediation: inspect the pinned policy source and redo the test matrix.
Sources#
- AOSP, SELinux concepts, current documentation, accessed 2026-10-26: labels, domains, access decisions and enforcing model.
- AOSP
system/sepolicy/private/shell.teatandroid-14.0.0_r1, specifically theconfig_gzread rule andperf_eventallow/neverallow pair cited above. The lesson quotes only source lines and does not claim the synthetic AVC is real. - AOSP, SELinux implementation, customization, and validation, current documentation, accessed 2026-10-26; exact vendor policy/runtime may differ.
Next: Rooting Methods