Skip to content

LDD-tree resolution - #5

Draft
RazeLighter777 wants to merge 1 commit into
landlock-lsm:mainfrom
RazeLighter777:lddtree-pr
Draft

LDD-tree resolution#5
RazeLighter777 wants to merge 1 commit into
landlock-lsm:mainfrom
RazeLighter777:lddtree-pr

Conversation

@RazeLighter777

@RazeLighter777 RazeLighter777 commented Dec 13, 2025

Copy link
Copy Markdown

Adds automatic resolution of binaries and their dependencies to island run, and creates access rules granting access to them at runtime.

[justin@zenbox island (lddtree-pr *$%|u=)]$ cargo run -- run -p test5 -v uname -a
+ command cargo run -- run -p test5 -v uname -a
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/island run -p test5 -v uname -a`
Using explicit profiles: ["test5"]
Using 1 profile(s): test5
... [omitted]
Resolved command path: /nix/store/imad8dvhp77h0pjbckp6wvmnyhp8dpgg-coreutils-9.8/bin/coreutils
Allowing dependency path: /nix/store/imad8dvhp77h0pjbckp6wvmnyhp8dpgg-coreutils-9.8/bin/coreutils
Allowing dependency path: /nix/store/12qcfbv6bgmxi1jd2dcddy25ijpcgj60-attr-2.5.2/lib/libattr.so.1.1.2502
Allowing dependency path: /nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/ld-linux-x86-64.so.2
Allowing dependency path: /nix/store/fwfpzqrvzhpjp91rbnq64z07diyicbwh-acl-2.3.2/lib/libacl.so.1.1.2302
Allowing dependency path: /nix/store/2f7yzmvcsydw38zr369l9xpk63v1ld5q-gmp-with-cxx-6.3.0/lib/libgmp.so.10.5.0
Allowing dependency path: /nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/libc.so.6
Allowing dependency path: /nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66/lib/ld-linux-x86-64.so.2
... [omitted]
Executing: uname
Linux zenbox 6.18.0 #1-NixOS SMP PREEMPT_DYNAMIC Sun Nov 30 22:42:10 UTC 2025 x86_64 GNU/Linux

This feature can be disabled at runtime with the --no-ldd flag.

It adds two crates, lddtree and which. lddtree does the ELF parsing and dependency resolution, and which resolves the command from the path.

Issue with lddtree (workaround added)

Currently, for some reason, lddtree does not properly resolve the full paths of n>1 order dependencies. For example, libcap in the top command on nixos is a dependency of the libsystemd package. lddtree will have libcap.so.2 listed as a dependency, but not have the absolute path to the lib, only the library name.

My hypothesis: lddtree only searches for absolute paths using the binary's RUNPATH/RPATHs. But some systems (notably nixos), also rely on each library's RUNPATH/RPATHs, and may not include the full paths of n>1 dependencies in every binary.

A workaround for this was implemented, but this may be useful submitted upstream.

Todo:

  • Gracefully handle this feature for non-ELF executables
  • Parse path and automatically add dependencies and libraries from there
  • Add tests for this feature
  • Document feature in README.md

Comment thread src/main.rs Outdated
WhichError(#[from] which::Error),
}

fn lddtree_collect_extra_library_paths(tree: &DependencyTree) -> Vec<PathBuf> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the ldd/elf helpers to a new elf.rs file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me. Will do.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. let me know if anything else should be moved.

Comment thread src/main.rs Outdated
help = "Disable shared-library dependency resolution (lddtree)",
long_help = "Disable shared-library dependency resolution using lddtree. When set, Island will not automatically allow shared library dependencies; only the resolved command path will be allowed. Use this if your profile already grants the necessary access or if you want fully declarative dependency access rules."
)]
no_ldd: bool,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar feature could be implemented for other interpreters, so a --no-dependency=elf seems more generic.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, thinking more about it, we should probably get rid of this CLI option and only add a profile's configuration instead. Something like no_dependency = ["elf"]. 🤔

@RazeLighter777 RazeLighter777 Dec 15, 2025

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense but how should this work with merging profiles?

If profile A and B are selected, and A specifies no elf dependencies but B doesn't, should the elf dependencies be resolved?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it would make more sense to do the union of the no_dependency from all active profiles.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I added a feature for this with a top level config option no_dependency. Right now elf is the only valid array member. It does a union of all the profiles for this.

@RazeLighter777

Copy link
Copy Markdown
Author

Gonna do a rebase here in a bit to clean up the commits.

Adds ELF lddtree resolution to island.

Don't even add the binary being ran if no_dependency = ["elf"]

Signed-off-by: Justin Suess <utilityemal77@gmail.com>
@RazeLighter777

Copy link
Copy Markdown
Author

I rebased it on main and it should merge cleanly now. but I still need to figure out how handle interpreted (non-elf) executables.

@RazeLighter777

Copy link
Copy Markdown
Author

One issue I'm running into with trusting $PATH (next on the TODO list) is the performance. I'm testing an initial implementation.

Recursively trusting every shared object in the path is expensive, even with some smart "visited" logic and symlink handling. We have to open every elf file, read its magic bytes and headers, etc.

It's an IO bound problem.

It takes about 10 seconds to add every program on $PATH and it's dependencies to the ruleset on my system with a good cpu, 128gb ram, and an NVME disk.

I have a couple of options for you ordered by how good I think they are:

  1. Cache the ldd trees for binaries in XDG_CACHE_HOME/island, (maybe with the cache key being the inode last modified time, and inode number?). Pro: Increases performance of subsequent runs and neatly handles system updates. We only have to recheck the mtime. Con: Adds code complexity.

  2. Have the user manually add additional binaries to the profile, whitelisting specific programs instead of reading all of $PATH. Pro: Much faster and more fine-grained. Con: Requires user intervention.

  3. Wait for the landlock mutable domains/supervisor feature. Then dynamically add binaries and libs on an as-needed basis.

  4. Do multiprocessing/asynchronous IO (bad idea in my opinion😅wouldn't save that much time and complicate everything)

Let me know what you think. If you want this $PATH feature to be a seperate PR, or if I'm misunderstanding your intention entirely just say the word.

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.

2 participants