Skip to content

feat(vfs): decode UDF PATH_COMPONENT symlinks - #14

Open
frankmanzhu wants to merge 3 commits into
SecurityRonin:mainfrom
frankmanzhu:main
Open

feat(vfs): decode UDF PATH_COMPONENT symlinks#14
frankmanzhu wants to merge 3 commits into
SecurityRonin:mainfrom
frankmanzhu:main

Conversation

@frankmanzhu

Copy link
Copy Markdown

feat(vfs): decode UDF PATH_COMPONENT symlinks

What this does

The vfs adapter now classifies UDF symbolic links (ICB Tag File Type 0x0c) as NodeKind::Symlink and implements FileSystem::read_link by decoding the ECMA-167 4/14.16.2 PATH_COMPONENT record chain with the Linux kernel's udf_pc_to_char semantics (fs/udf/symlink.c + unicode.c).

Why it currently fails

read_link was unimplemented (returned an empty target) and symlink nodes were surfaced as NodeKind::File with the raw 19-byte PATH_COMPONENT record as file content. A consumer that extracts files from a UDF image containing symlinks silently writes the record bytes as a regular file — wrong data with no error or warning. Verified against a real Linux-driver-authored symlink: macOS's hdiutil attach resolves the same record to ../README.txt, while the unpatched adapter read it as a 19-byte file.

Why it needs fixing

The forensic-vfs::FileSystem contract promises NodeKind::Symlink + read_link as first-class. Extraction tools (7-Zip, macOS, the Linux UDF driver itself) all handle UDF symlinks; the adapter was the odd one out, and the failure mode (silent junk bytes as a file) is the worst kind for a forensic/extraction consumer.

Implementation notes

  • The decoder follows the kernel byte-for-byte: 4-byte records (componentType, lengthComponentIdent, __le16 componentFileVersionNum — stable across kernel v6.6/v6.8/v6.14), types 1–5 (root/parent/.././CS0 name), the trailing-slash trim, and the type-1-with-location break (an agreed media-specific location yields an empty target — never a fabricated path).
  • Record bounds are checked at every step; hostile chains cannot panic.
  • The per-child classification reads each child's File Entry type (the FID carries only the directory bit), with a fallback to the FID characteristics when the child FE sector is missing.

Test coverage

  • Committed fixture tests/data/udf_symlink.img (provenance + verbatim mint command in tests/data/README.md): a mkudffs 2.01 volume with a Linux-driver-authored symlink; the integration test asserts NodeKind::Symlink + read_link == b"../README.txt" — the Linux driver's own resolution as ground truth. The image also seeds the fuzz corpus automatically.
  • Synthetic-image tests (test_support): the in-memory image now carries a symlink File Entry (covering classification, decode, the FID-fallback path via the existing BROKEN_DIR_FE, and the loud error when a symlink's allocation descriptors are unreadable).
  • Branch-level decoder tests: relative/absolute/UTF-16 targets, parent reset, current-dir records, agreed-location break, bounds safety.
  • cargo llvm-cov --workspace --all-features: 96.36% lines (the repo's CI floor is 96).

Code quality

  • cargo clippy --workspace --all-targets --all-features: clean. cargo test --workspace --all-features: all green (67 lib tests + integration + doc).
  • No unsafe, no unwrap/expect in production code; failure paths return VfsError with layer/offset context.

The vfs adapter now classifies ICB file type 0x0c (symbolic link) as
NodeKind::Symlink and implements FileSystem::read_link by decoding the
ECMA-167 4/14.16.2 PATH_COMPONENT record chain with the Linux kernel's
udf_pc_to_char semantics (4-byte records: type, length, __le16 version;
types 1-5: root/parent/.. /./CS0 name; trailing slash trimmed).

Verified against a mkudffs 2.01 image whose symlink was authored by the
Linux 6.8 UDF driver (target '../README.txt'); macOS hdiutil resolves
the same record identically.
- Committed udf_symlink.img (mkudffs 2.01 + Linux-driver-authored
  PATH_COMPONENT symlink; provenance in tests/data/README.md, seeds the
  fuzz corpus like the other images).
- Integration test walks the committed image: Symlink classification and
  read_link == '../README.txt' (the Linux driver's own resolution).
- Synthetic test_support image now carries a symlink File Entry (type 0x0c,
  inline PATH_COMPONENT data) plus its root FID; the root directory moved
  to its own block behind a short allocation descriptor since the inline
  area overflowed. New vfs tests cover classification, decode, the
  FID-fallback path (BROKEN_DIR_FE), and the loud error when a symlink's
  allocation descriptors are unreadable.
- Decoder tests cover the agreed-location break, current-dir records, and
  the parent reset.
- llvm-cov --workspace --all-features: 96.36% lines (floor 96).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant