Project 3: Custom Application Policy Module Builder

Write a full SELinux policy module for a custom daemon, including types, transitions, and file contexts.

Quick Reference

Attribute Value
Difficulty Level 3: Advanced
Time Estimate 2-3 weeks
Main Programming Language Policy language (TE + M4)
Alternative Programming Languages Python (for generation tooling), C
Coolness Level Level 4
Business Potential 2
Prerequisites Projects 1-2, SELinux tooling, service management basics
Key Topics TE rules, domain transitions, policy modules, file contexts

1. Learning Objectives

By completing this project, you will:

  1. Define new SELinux types for a custom application and its data.
  2. Create domain transitions and entrypoints for a systemd-managed service.
  3. Build and load a policy module using the SELinux toolchain.
  4. Label application files and directories with custom types.
  5. Validate least privilege by iterating on AVC denials.

2. All Theory Needed (Per-Concept Breakdown)

SELinux Policy Language and Type Enforcement

Fundamentals

SELinux policy is a declarative language that specifies which source types can access which target types under which object classes and permissions. The dominant model in most distributions is Type Enforcement (TE). An allow rule in TE looks like allow source_t target_t:class { perms }; and it is the core unit of access control. Policies also define types, attributes, roles, and constraints. For a custom application, you need to define a process type (domain), file types for data and logs, and allow rules that connect them. Understanding the structure of a policy module and how rules are interpreted is essential to writing a correct and minimal policy.

Deep Dive into the concept

The SELinux policy language is compiled into a binary policy that the kernel loads and enforces. Policies are built from multiple modules that are layered together, which means your custom policy module must integrate with the base policy. In practice, you define a type for your domain (e.g., myapp_t), a type for your executable (myapp_exec_t), and additional types for data (myapp_data_t) and logs (myapp_log_t). The domain is associated with the executable via a type_transition rule, and the executable type must be marked as an entrypoint for the domain. Without the entrypoint permission, the transition will not occur.

Type enforcement relies on attributes to simplify policy. For example, files_type marks types that can appear on files, while domain marks process types. You can attach your custom types to existing attributes to inherit policy conventions, but you must do so carefully to avoid unintended access. The policy language also supports interfaces and macros (via M4) that encapsulate common patterns. The sepolicy generate tool produces a scaffolding that uses these interfaces, which can accelerate development. However, blindly using macros can grant more permissions than required. The correct approach is to start minimal, run the application, capture AVC denials, and iteratively add the specific allows needed.

Constraints and neverallow rules further restrict access. They are evaluated after allow rules, and they can prevent unsafe permissions even if you add them. This is a safety net but can be surprising. If you attempt to grant access that violates a constraint, module compilation may fail or the rule may be ignored. Understanding these constraints is part of responsible policy development. Use sesearch and seinfo to inspect existing policy and ensure your module aligns with it.

Because SELinux is a kernel-enforced MAC, your policy decisions are final. Overly permissive rules can defeat the protection you are trying to build. Least privilege is not just a principle here; it is a debugging strategy. If you grant only what the app needs and verify it through AVCs, you build an auditable, minimal policy. This iterative method also makes policies maintainable, as each rule corresponds to a real access pattern. Your policy module is, therefore, not just code; it is documentation of the application’s security contract.

Additional operational notes on SELinux Policy Language and Type Enforcement: In real systems, this concept interacts with policy versions, distribution defaults, and local overrides. Always record the exact policy version and runtime toggles when diagnosing behavior, because the same action can be allowed on one host and denied on another. When you change configuration related to this concept, capture before/after evidence (labels, logs, and outcomes) so you can justify the change, detect regressions, and roll it back if needed. Treat every tweak as a hypothesis: change one variable, re-run the same action, and compare results against a known baseline. This makes debugging repeatable and keeps your fixes defensible.

From a design perspective, treat SELinux Policy Language and Type Enforcement as an invariant: define what success means, which data proves it, and what failure looks like. Build tooling that supports dry-run mode and deterministic fixtures so you can validate behavior without risking production. This also makes the concept teachable to others. Finally, connect the concept to security and performance trade-offs: overly broad changes reduce security signal, while overly strict changes create operational friction. Good designs surface these trade-offs explicitly so operators can make safe decisions.

How this fit on projects

TE rules appear in §3.2 and §4.4, and they are validated in §6.2. This concept is the core of P02-avc-denial-analyzer-auto-fixer.md and P08-selinux-policy-diff-tool.md.

Definitions & key terms

  • type -> named label used in TE
  • allow rule -> grants access between types
  • attribute -> grouping of types for policy convenience
  • interface -> macro that encapsulates common policy patterns
  • neverallow -> rule that forbids access regardless of allow rules

Mental model diagram

myapp_t --allow--> myapp_data_t:file { read write }

How it works (step-by-step, with invariants and failure modes)

  1. Define types for domain and files.
  2. Attach attributes (domain, files_type) if appropriate.
  3. Write allow rules for required access.
  4. Compile the policy module.
  5. Load module and validate behavior.

Invariants: allow rules are type-based; no access without explicit allow. Failure modes: rule too broad, constraint violations, missing attributes.

Minimal concrete example

type myapp_t;
allow myapp_t myapp_log_t:file { append write getattr };

Common misconceptions

  • “A single allow rule is enough.” -> Real apps need permissions across multiple classes.
  • “Macros always grant minimal access.” -> They can be broad if used carelessly.

Check-your-understanding questions

  1. Why are neverallow rules important?
  2. How do attributes simplify policy?
  3. What is the risk of overusing macros?

Check-your-understanding answers

  1. They enforce global security invariants and prevent unsafe permissions.
  2. They let you apply rules to groups of types instead of each type individually.
  3. Macros can add permissions you did not intend, weakening least privilege.

Real-world applications

  • Confining custom daemons in enterprises.
  • Hardening vendor software with custom policies.

Where you’ll apply it

References

  • “SELinux by Example” (Mayer et al.), policy language chapters
  • “SELinux Notebook” policy reference

Key insights

SELinux policy is a precise contract: every allow rule should correspond to a real need.

Summary

Type Enforcement rules define access. Writing minimal allow rules is the foundation of safe SELinux policy.

Homework/Exercises to practice the concept

  1. Write a minimal allow rule for reading a config file.
  2. Identify an attribute you could attach your custom type to.
  3. Find a neverallow rule in the base policy.

Solutions to the homework/exercises

  1. allow myapp_t myapp_conf_t:file { read getattr open };
  2. files_type for file labels, domain for process types.
  3. Use sesearch --neverallow to locate constraints.

Domain Transitions and Entrypoints

Fundamentals

Domain transitions ensure that a process runs in the correct SELinux domain when it executes a specific binary. The transition is defined by a type_transition rule and an entrypoint permission on the binary’s type. Without these, the process will remain in its parent domain, which is often too permissive. For a custom application, you must define the executable type (myapp_exec_t) and a transition from init_t or systemd_t into your custom domain (myapp_t). This is essential for confinement and is one of the first things to verify during debugging.

Deep Dive into the concept

A domain transition is a policy rule that maps from a source domain and an executable type to a new process domain. It is evaluated when a process calls execve on a binary. The rule looks like type_transition source_t myapp_exec_t:process myapp_t; and is accompanied by allow source_t myapp_exec_t:file { read execute entrypoint };. The entrypoint permission is what authorizes the binary as a valid transition target. Without it, SELinux will not transition even if the type_transition is declared. This is a common error when hand-writing policy.

Systemd complicates this slightly because many services are spawned by systemd (or init) which run in init_t or systemd_t domains. If your policy does not allow that domain to execute your binary as an entrypoint, the service will run in the caller domain. You can verify domain transitions by checking ps -eZ after starting the service. If your daemon is running in init_t, your transition rules are missing or the binary label is wrong.

There are also role transitions and MLS constraints that can affect transitions, especially on MLS systems. In targeted policy, these are less visible, but you should still ensure your types have the correct role associations. Another nuance is that some transitions are confined by domain_auto_trans or other macros. These macros set up the entrypoint, type_transition, and sometimes additional permissions in one step. If you use a macro, understand what it expands to by inspecting the generated policy. This helps you avoid unnecessary permissions.

From a debugging perspective, transition errors manifest as policy failures that look unrelated. If the service runs in the wrong domain, every subsequent access check will be evaluated against the wrong set of rules, causing a cascade of denials or unexpected allowances. That is why your policy builder must validate transitions early and make them explicit in the module documentation.

Operational expansion for Domain Transitions and Entrypoints: In real systems, the behavior you observe is the product of policy, labels, and runtime state. That means your investigation workflow must be repeatable. Start by documenting the exact inputs (contexts, paths, users, domains, ports, and the action attempted) and the exact outputs (audit events, error codes, and any policy query results). Then, replay the same action after each change so you can attribute cause and effect. When the concept touches multiple subsystems, isolate variables: change one label, one boolean, or one rule at a time. This reduces confusion and prevents accidental privilege creep. Use staging environments or fixtures to test fixes before deploying them widely, and always keep a rollback path ready.

To deepen understanding, connect Domain Transitions and Entrypoints to adjacent concepts: how it affects policy decisions, how it appears in logs, and how it changes operational risk. Build small verification scripts that assert the expected outcome and fail loudly if the outcome diverges. Over time, these scripts become a regression suite for your SELinux posture. Finally, treat the concept as documentation-worthy: write down the invariants it guarantees, the constraints it imposes, and the exact evidence that proves it works. This makes future debugging faster and creates a shared mental model for teams.

How this fit on projects

TE rules appear in §3.2 and §4.4, and they are validated in §6.2. This concept is the core of P02-avc-denial-analyzer-auto-fixer.md and P08-selinux-policy-diff-tool.md.

Further depth on Domain Transitions and Entrypoints: In production environments, this concept is shaped by policy versions, automation layers, and distro-specific defaults. To keep reasoning consistent, capture a minimal evidence bundle every time you analyze behavior: the policy name/version, the exact labels or contexts involved, the command that triggered the action, and the resulting audit event. If the same action yields different decisions on two hosts, treat that as a signal that a hidden variable changed (boolean state, module priority, label drift, or category range). This disciplined approach prevents trial-and-error debugging and makes your conclusions defensible.

Operationally, build a short checklist for Domain Transitions and Entrypoints: verify prerequisites, verify labels or mappings, verify policy query results, then run the action and confirm the expected audit outcome. Track metrics that reflect stability, such as the count of denials per hour, the number of unique denial keys, or the fraction of hosts in compliance. When you must change behavior, apply the smallest change that can be verified (label fix before boolean, boolean before policy). Document the rollback path and include a post-change validation step so the system returns to a known-good state.

Definitions & key terms

  • type -> named label used in TE
  • allow rule -> grants access between types
  • attribute -> grouping of types for policy convenience
  • interface -> macro that encapsulates common policy patterns
  • neverallow -> rule that forbids access regardless of allow rules

Mental model diagram

myapp_t --allow--> myapp_data_t:file { read write }

How it works (step-by-step, with invariants and failure modes)

  1. Define types for domain and files.
  2. Attach attributes (domain, files_type) if appropriate.
  3. Write allow rules for required access.
  4. Compile the policy module.
  5. Load module and validate behavior.

Invariants: allow rules are type-based; no access without explicit allow. Failure modes: rule too broad, constraint violations, missing attributes.

Minimal concrete example

type myapp_t;
allow myapp_t myapp_log_t:file { append write getattr };

Common misconceptions

  • “A single allow rule is enough.” -> Real apps need permissions across multiple classes.
  • “Macros always grant minimal access.” -> They can be broad if used carelessly.

Check-your-understanding questions

  1. Why are neverallow rules important?
  2. How do attributes simplify policy?
  3. What is the risk of overusing macros?

Check-your-understanding answers

  1. They enforce global security invariants and prevent unsafe permissions.
  2. They let you apply rules to groups of types instead of each type individually.
  3. Macros can add permissions you did not intend, weakening least privilege.

Real-world applications

  • Confining custom daemons in enterprises.
  • Hardening vendor software with custom policies.

Where you’ll apply it

References

  • “SELinux by Example” (Mayer et al.), policy language chapters
  • “SELinux Notebook” policy reference

Key insights

SELinux policy is a precise contract: every allow rule should correspond to a real need.

Summary

Type Enforcement rules define access. Writing minimal allow rules is the foundation of safe SELinux policy.

Homework/Exercises to practice the concept

  1. Write a minimal allow rule for reading a config file.
  2. Identify an attribute you could attach your custom type to.
  3. Find a neverallow rule in the base policy.

Solutions to the homework/exercises

  1. allow myapp_t myapp_conf_t:file { read getattr open };
  2. files_type for file labels, domain for process types.
  3. Use sesearch --neverallow to locate constraints.

Policy Module Build and Deployment Pipeline

Fundamentals

Policy modules are compiled units that can be loaded into the SELinux policy store without rebuilding the entire base policy. The typical workflow is to write .te and .fc files, compile them with checkmodule and semodule_package, and load them with semodule -i. For quick iteration, the SELinux development Makefile automates this. Understanding this pipeline ensures your policy is built correctly and can be installed, updated, or removed cleanly. It also supports versioning and rollback, which are essential in production.

Deep Dive into the concept

SELinux policy compilation is a multi-step process because the human-readable policy language must be transformed into a binary representation. checkmodule compiles the .te file into a .mod file, which contains the module in an intermediate format. semodule_package bundles the .mod with file contexts (.fc) into a .pp package. Finally, semodule -i loads the package into the policy store. This pipeline ensures that syntax errors or constraint violations are caught at build time. When using /usr/share/selinux/devel/Makefile, you can simply run make -f ... myapp.pp and it will call the correct tools.

Module priority is another operational detail. SELinux supports module priorities that determine which rules override others. If you define a type in a module with a lower priority than a conflicting definition in another module, the policy store will resolve conflicts according to priority. Most custom modules use priority 100 or the default. In this project, you should avoid name collisions and keep types unique to your app to prevent conflicts.

Deployment is also about lifecycle. semodule -i installs, semodule -r removes, and semodule -l lists installed modules. Your project should include a Makefile target for install/uninstall so that testers can reproduce steps. When you update a policy, the AVC cache is invalidated and the new policy becomes active immediately. This is good for iterative development but means you should be cautious when testing on production systems. Always test in a VM first, and include a rollback plan.

Another subtle point is that file context rules are part of the policy package but are not applied automatically to existing files. After installing a module, you must run restorecon on the relevant paths. Many failures during policy testing are caused by forgetting this step. Your implementation guide should include these commands explicitly.

Operational expansion for tart the service and check the domain.: In real systems, the behavior you observe is the product of policy, labels, and runtime state. That means your investigation workflow must be repeatable. Start by documenting the exact inputs (contexts, paths, users, domains, ports, and the action attempted) and the exact outputs (audit events, error codes, and any policy query results). Then, replay the same action after each change so you can attribute cause and effect. When the concept touches multiple subsystems, isolate variables: change one label, one boolean, or one rule at a time. This reduces confusion and prevents accidental privilege creep. Use staging environments or fixtures to test fixes before deploying them widely, and always keep a rollback path ready.

To deepen understanding, connect tart the service and check the domain. to adjacent concepts: how it affects policy decisions, how it appears in logs, and how it changes operational risk. Build small verification scripts that assert the expected outcome and fail loudly if the outcome diverges. Over time, these scripts become a regression suite for your SELinux posture. Finally, treat the concept as documentation-worthy: write down the invariants it guarantees, the constraints it imposes, and the exact evidence that proves it works. This makes future debugging faster and creates a shared mental model for teams.

How this fit on projects

Domain transitions are part of §3.2 and verified in §3.7. They are also relevant in P01-selinux-context-explorer-visualizer.md.

Further depth on tart the service and check the domain.: In production environments, this concept is shaped by policy versions, automation layers, and distro-specific defaults. To keep reasoning consistent, capture a minimal evidence bundle every time you analyze behavior: the policy name/version, the exact labels or contexts involved, the command that triggered the action, and the resulting audit event. If the same action yields different decisions on two hosts, treat that as a signal that a hidden variable changed (boolean state, module priority, label drift, or category range). This disciplined approach prevents trial-and-error debugging and makes your conclusions defensible.

Operationally, build a short checklist for tart the service and check the domain.: verify prerequisites, verify labels or mappings, verify policy query results, then run the action and confirm the expected audit outcome. Track metrics that reflect stability, such as the count of denials per hour, the number of unique denial keys, or the fraction of hosts in compliance. When you must change behavior, apply the smallest change that can be verified (label fix before boolean, boolean before policy). Document the rollback path and include a post-change validation step so the system returns to a known-good state.

Definitions & key terms

  • type_transition -> rule mapping exec to new domain
  • entrypoint -> permission granting executable as transition target
  • exec type -> label on the binary
  • source domain -> domain executing the binary

Mental model diagram

init_t --exec(myapp_exec_t)--> myapp_t

How it works (step-by-step, with invariants and failure modes)

  1. Service manager in init_t executes /usr/sbin/myapp.
  2. SELinux checks entrypoint permissions on myapp_exec_t.
  3. If allowed, apply type_transition and assign myapp_t.
  4. Process runs in myapp_t and policy rules apply.

Invariants: binary must be labeled correctly; transition rule must exist. Failure modes: mislabeled binary, missing entrypoint, missing type_transition.

Minimal concrete example

type myapp_exec_t;
type myapp_t;
init_daemon_domain(myapp_t, myapp_exec_t)

Common misconceptions

  • “Labeling the binary is enough.” -> You still need type_transition/entrypoint rules.
  • “Systemd uses a different transition system.” -> It still relies on SELinux transitions.

Check-your-understanding questions

  1. What happens if the binary label is wrong?
  2. Why is entrypoint permission required?
  3. How do you verify a transition occurred?

Check-your-understanding answers

  1. The process stays in the parent domain, often init_t.
  2. It marks the binary as a valid transition target to prevent arbitrary exec.
  3. Use ps -eZ to confirm the process domain.

Real-world applications

  • Confining web servers, database daemons, and custom services.
  • Preventing privilege escalation via unconfined domains.

Where you’ll apply it

References

  • “SELinux by Example” (Mayer et al.), domain transitions
  • Red Hat SELinux Guide, service domains

Key insights

If the transition is wrong, all subsequent policy reasoning is wrong.

Summary

Domain transitions are the gateway to confinement. They must be explicit and verified.

Homework/Exercises to practice the concept

  1. Label a test binary and create a transition into a custom domain.
  2. Verify transition with ps -eZ.
  3. Break the label and observe the effect.

Solutions to the homework/exercises

  1. Use semanage fcontext to label and define type_transition in policy.
  2. Start the service and check the domain.
  3. restorecon to correct the label and confirm the transition is restored.

3. Project Specification

3.1 What You Will Build

A complete SELinux policy module for a custom daemon named myapp, including:

  • A process domain myapp_t
  • An executable type myapp_exec_t
  • File types for configuration, data, and logs
  • A systemd transition into myapp_t
  • A reproducible build and install process

Excluded features:

  • Full distribution policy packaging
  • GUI management tools

3.2 Functional Requirements

  1. Types: Define process, exec, config, data, and log types.
  2. Transitions: Implement a domain transition from init_t/systemd_t.
  3. Allow Rules: Grant minimal permissions needed for startup and operation.
  4. File Contexts: Provide .fc mappings for all app paths.
  5. Packaging: Provide Makefile targets to build/install/remove.

3.3 Non-Functional Requirements

  • Security: Enforce least privilege and avoid broad type attributes.
  • Reliability: Service must start and run without AVC denials.
  • Maintainability: Policy should be readable and documented.

3.4 Example Usage / Output

$ make -f /usr/share/selinux/devel/Makefile myapp.pp
$ sudo semodule -i myapp.pp
$ sudo restorecon -Rv /usr/sbin/myapp /var/lib/myapp /var/log/myapp
$ sudo systemctl start myapp
$ ps -eZ | grep myapp
system_u:system_r:myapp_t:s0  2441 ? 00:00:00 myapp

3.5 Data Formats / Schemas / Protocols

Policy files:

  • myapp.te (types, allow rules, transitions)
  • myapp.fc (file context regex mappings)

3.6 Edge Cases

  • Service runs in init_t because binary mislabeled
  • Missing file context rules for /opt/myapp
  • AVC denials for socket access or PID files

3.7 Real World Outcome

3.7.1 How to Run (Copy/Paste)

make -f /usr/share/selinux/devel/Makefile myapp.pp
sudo semodule -i myapp.pp
sudo restorecon -Rv /usr/sbin/myapp /var/lib/myapp /var/log/myapp
sudo systemctl restart myapp

3.7.2 Golden Path Demo (Deterministic)

Use a fixture systemd unit and test files under /var/lib/myapp. Verify that ps -eZ shows myapp_t and that ausearch -m avc -c myapp -ts recent returns no denials.

3.7.3 CLI Transcript (Success and Failure)

$ sudo systemctl start myapp
$ ps -eZ | grep myapp
system_u:system_r:myapp_t:s0  2441 ? 00:00:00 myapp
Exit code: 0

$ sudo semodule -r myapp
$ sudo systemctl start myapp
Job failed: see journalctl -xe
Exit code: 1

4. Solution Architecture

4.1 High-Level Design

+-------------+     +----------------+     +-------------------+
| myapp_exec  | --> | Domain Transition| --> | myapp_t (process) |
+-------------+     +----------------+     +-------------------+
         |                                           |
         v                                           v
  myapp_data_t, myapp_log_t                     allow rules

4.2 Key Components

Component Responsibility Key Decisions
Types Define domain and file types Use dedicated types for logs/data
Transitions Ensure service runs in myapp_t Use init_daemon_domain macro
Allow Rules Grant least privilege Add permissions via AVC feedback
File Contexts Label app paths Use regex for nonstandard paths

4.3 Data Structures (No Full Code)

# myapp.te
module myapp 1.0;

require {
  type init_t;
}

# types
# allow rules

4.4 Algorithm Overview

Key Algorithm: Iterative Policy Tightening

  1. Run service in enforcing mode.
  2. Collect AVC denials with ausearch.
  3. Add minimal allow rules or fix labels.
  4. Repeat until denials are resolved.

Complexity Analysis:

  • Time: O(n) denials per iteration
  • Space: O(1) incremental additions

5. Implementation Guide

5.1 Development Environment Setup

sudo dnf install -y selinux-policy-devel policycoreutils-devel

5.2 Project Structure

myapp-policy/
├── myapp.te
├── myapp.fc
├── Makefile
└── README.md

5.3 The Core Question You’re Answering

“How do I confine a new service with only the permissions it truly needs?”

5.4 Concepts You Must Understand First

  1. TE allow rule structure and type definitions.
  2. Domain transitions and entrypoints.
  3. Module build and install pipeline.

5.5 Questions to Guide Your Design

  1. What files does the app need to access, and with which permissions?
  2. Which domain should the service run under?
  3. How will you verify the policy is minimal?

5.6 Thinking Exercise

List every file or socket the service touches on startup. For each, decide whether to label it or allow access.

5.7 The Interview Questions They’ll Ask

  1. “What is a type_transition rule?”
  2. “Why should log files have a dedicated type?”
  3. “How do you iterate on a policy safely?”

5.8 Hints in Layers

Hint 1: Use sepolicy generate for a scaffold

Hint 2: Start with minimal permissions and add only what AVCs show

Hint 3: Verify domain with ps -eZ after each change

5.9 Books That Will Help

Topic Book Chapter
Policy language “SELinux by Example” Ch. 3-4
Labeling “SELinux System Administration” Labeling chapter
Services “The Linux Programming Interface” Daemons and services

5.10 Implementation Phases

Phase 1: Foundation (3-4 days)

Goals:

  • Define types and file contexts.
  • Create a policy scaffold.

Tasks:

  1. Label your binary and directories.
  2. Use sepolicy generate or write a basic .te file.

Checkpoint: Module compiles successfully.

Phase 2: Core Functionality (1 week)

Goals:

  • Implement transitions and minimal allow rules.
  • Run in enforcing mode and fix denials.

Tasks:

  1. Start service, collect AVCs.
  2. Add precise allow rules and relabel paths.

Checkpoint: Service runs with zero AVCs.

Phase 3: Polish & Edge Cases (3-4 days)

Goals:

  • Document policy reasoning.
  • Add uninstall and rollback steps.

Tasks:

  1. Add README and risk notes.
  2. Provide make uninstall target.

Checkpoint: Policy can be installed and removed cleanly.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Logging type var_log_t vs custom custom (myapp_log_t) Avoids broad permissions
Transition macro init_daemon_domain vs manual macro + review Faster and safer
Policy iteration permissive domain vs enforcing enforcing with AVC review Avoids masked errors

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Policy Tests Validate transitions ps -eZ domain checks
Integration Tests Service operations read/write log, create PID file
Edge Case Tests Missing labels unlabeled data dir

6.2 Critical Test Cases

  1. Service starts and runs in myapp_t.
  2. Logs are written only to myapp_log_t.
  3. Removing the module causes a predictable failure.

6.3 Test Data

fixtures/app_data/
fixtures/app_log/

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Missing file context for /opt/myapp Service denied access Add .fc rule and relabel
Transition missing Process runs in init_t Add type_transition + entrypoint
Overbroad allow rules Policy too permissive Remove and narrow rules

7.2 Debugging Strategies

  • Use ausearch -m avc -c myapp after every change.
  • Verify labels with ls -Z and matchpathcon.

7.3 Performance Traps

  • Too many allow rules can slow policy evaluation slightly; keep them minimal.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a policy documentation section with rationale.
  • Include a make uninstall target.

8.2 Intermediate Extensions

  • Add separate types for cache vs data.
  • Support an optional network port with a custom port type.

8.3 Advanced Extensions

  • Write a policy interface for reuse by other modules.
  • Add MLS/MCS constraints if applicable.

9. Real-World Connections

9.1 Industry Applications

  • Confining internal services and compliance workloads.
  • Auditing vendor applications for least privilege.
  • SELinux reference policy (as a model for structure).
  • setools (sesearch, seinfo) for policy analysis.

9.3 Interview Relevance

  • Policy design and least privilege reasoning.
  • Understanding of SELinux transitions and module deployment.

10. Resources

10.1 Essential Reading

  • “SELinux by Example” (policy chapters)
  • “SELinux System Administration” (labeling and troubleshooting)

10.2 Video Resources

  • SELinux policy writing workshop sessions

10.3 Tools & Documentation

  • checkmodule, semodule, sepolicy generate

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain TE rule shape and how types interact.
  • I can explain domain transitions and entrypoints.
  • I can explain the policy build pipeline.

11.2 Implementation

  • Module installs and removes cleanly.
  • Service runs in myapp_t.
  • No AVC denials remain in enforcing mode.

11.3 Growth

  • I can justify each allow rule in the policy.
  • I documented decisions and trade-offs.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Custom domain and file types exist.
  • Service runs in correct domain.

Full Completion:

  • All required resources are labeled and accessed without denials.
  • Policy module documented and reproducible.

Excellence (Going Above & Beyond):

  • Policy interface for reuse and MLS-compatible constraints.

13 Additional Content Rules (Hard Requirements)

13.1 Determinism

  • Use a fixed test service and fixtures for reproducible AVCs.

13.2 Outcome Completeness

  • Include success and failure paths in Real World Outcome.

13.3 Cross-Linking

  • Link to related projects and relevant sections.

13.4 No Placeholder Text

  • All sections include concrete guidance.