Linux Security / Basic: identity, resources, and evidence / Lesson 4
Review paths, links, and resource identity
Distinguish lexical path text from kernel resolution, object identity, and trusted directory boundaries.
On this page
Lesson goals
- Explain why string-prefix checks do not establish filesystem containment.
- Preserve link and changing-path assumptions without accessing the host filesystem.
Treat a pathname as a lookup request
A pathname is interpreted from a starting directory or root through components under the processโs context. Relative paths, mount namespaces, symbolic links, directory permissions, and concurrent changes can alter which object is reached. A string that looks internal is not proof of a trusted object. Use the Linux path-resolution model to ask which directory is trusted and what can change during lookup. This lesson uses invented path strings only. It does not resolve, stat, open, create, remove, or modify a real path, link, mount, or device on this host.
Compare components without claiming containment
A raw prefix test can confuse /lab/reports-old with /lab/reports. The first program demonstrates that mistake and checks component equality for one fixed path using PurePosixPath, which does not access the filesystem. This is a lexical illustration, not a security validator. It does not handle actual symlinks, mounts, races, or all accepted input representations. Even a component match says only that the supplied string has that shape. A production design needs a reviewed directory-relative interface and resolution policy supported by the actual platform, with its assumptions verified separately.
from pathlib import PurePosixPath
base = PurePosixPath("/lab/reports")
candidate = PurePosixPath("/lab/reports-old/out.txt")
assert str(candidate).startswith(str(base)) is True
assert candidate.parts[:len(base.parts)] != base.parts
inside_text = PurePosixPath("/lab/reports/out.txt")
assert inside_text.parts[:len(base.parts)] == base.parts
print("raw prefix and lexical components answer different questions")
raw prefix and lexical components answer different questions
The complete program performs PurePosixPath string operations only. No filesystem lookup occurs. The final lexical match is not proof that a real resolved object stays inside a directory or has the required ownership and permissions.
Show why an alias can change the reached object
The second program models one declared alias from a path that looks inside the report tree to a different fixture object. It is a dictionary lookup, not a symlink resolver. It makes the trust question visible: who can control a component or target between validation and use? A path normalization helper cannot establish those facts. Hard links, symbolic links, bind mounts, and namespace views have different semantics and must not be merged into one generic redirect idea. Record the exact mechanism relevant to a real design before choosing a defense.
aliases = {"/lab/reports/current":"object-outside-report-scope"}
requested = "/lab/reports/current"
assert requested.startswith("/lab/reports/")
reached = aliases[requested]
assert reached == "object-outside-report-scope"
assert reached != "approved-report-object"
print("inside-looking text does not establish modeled object identity")
inside-looking text does not establish modeled object identity
The complete program uses invented strings and a supplied mapping. It creates no link and follows no real path. The result establishes a limitation of the text claim, not an actual filesystem escape or vulnerability in a product.
Keep trusted directory authority narrow
A file-handling design should identify which directory reference is trusted, who can mutate the namespace, and which resolution features are permitted. Linux provides directory-relative interfaces and, on supporting kernels, more specific resolution controls; support and exact semantics must be checked for the selected environment. Avoid teaching a universal sequence of precheck then open as race-safe. A successful check of one path can become stale before a later lookup. Use documented platform primitives in a reviewed implementation, not a homemade string sanitizer. Actual resource access and syscall behavior remain unexecuted in this course.
Verify object-level outcomes in an authorized environment
Test fixed legitimate names, sibling prefixes, parent components, unsupported forms, aliases, and controlled namespace changes in the appropriate approved fixture. Verify the actual object and unchanged protected state on refusal, not only the accepted path string. Keep ownership, permission, and mandatory-policy claims separate from lexical validation. These local examples establish only text and declared-mapping properties. They do not certify containment or harden a real application. A useful conclusion names exactly what the parser or model checked and which kernel-resolution and concurrency boundaries still need evidence.
Common errors and fixes
A startswith check proves containment
- Why it happens
- Text similarity is confused with component and object resolution.
- How to fix it
- Use a reviewed platform resolution design and verify actual outcomes.
- Prevent it next time
- Keep lexical checks explicitly limited.
Normalization proves a path cannot change
- Why it happens
- Namespace authority and timing are ignored.
- How to fix it
- Review links, writable components, and atomic resource access.
- Prevent it next time
- Test controlled check/use changes only in approved fixtures.
A dictionary alias demo is called a real escape test
- Why it happens
- The model is mistaken for kernel execution.
- How to fix it
- State the supplied mapping and unexecuted platform boundary.
- Prevent it next time
- Record actual versus illustrative evidence separately.
Check your understanding
1. Why does /lab/reports-old pass a raw /lab/reports prefix check?
Show worked solution
The character prefix does not include a component boundary. The fixed lexical example detects the sibling difference, but even correct component comparison cannot establish actual filesystem containment because resolution, links, mounts, and races remain outside the string model.
2. A path was checked before it was opened. What remains uncertain?
Show worked solution
Whether the later operation reached the same intended object under the same trusted namespace and credential context. A mutable component or alias can invalidate the earlier observation. Do not perform host experiments here; identify a supported atomic or constrained platform design and plan separate authorized verification.
Key takeaways
- A path string is not a complete resource identity.
- Lexical comparison does not prove kernel containment.
- Namespace control and check/use timing need explicit design.