Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

allow building multicall binary as dynamic library #7928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
Loading
from

Conversation

jtracey
Copy link
Contributor

@jtracey jtracey commented May 13, 2025

It was noted in the latest Ubuntu post that they were having problems with AppArmor profiles on the multicall binary, and listed a possible solution of

Build coreutils into a dynamic library that simply exposes coreutils_main() and then call that from tiny wrapper binaries

I decided to see how much work that would be, and it turns out to not be too bad. Basically, I just moved the multicall binary logic into a top-level library, and made the multicall binary link to it, either statically (i.e., normal Rust linking) or dynamically, depending on whether the "dynamic" feature is enabled. On my Debian system with --release, the normal coreutils (i.e., the default multicall binary) is ~14M, while when dynamically linking, libcoreutils.so is ~26M and coreutils (i.e., the minimal shim that loads and executes the shared library) is 381K.

This allows creating individual binaries with different profiles by just copying the dynamically linked multicall binary with the name of each respective utility. It's only marginally better for security though, since the behavior of the shim executable is identical to the full multicall binary, meaning someone could just modify argv[0] to get the coreutil foo to run with the AppArmor profile of coreutil bar, or exploit a confused deputy with setuid, etc. There are a few possible solutions to that (for a future PR), with the main gist being to get each util's (uu)main dynamically linked when the dynamic feature is enabled. Another thing I'm putting off is proper Windows support in the documentation and CICD. I don't know as much about Windows/.dlls as I do Unix shared libraries, and don't have a local Windows dev env right now, so I'm not testing it at all yet, but in theory it should work.

Some things I'd welcome any feedback on:

  • do we want this: Seems simple enough to support, but I'm open to arguments otherwise. There's also possible arguments for doing this in a fundamentally different way, such as writing the shim in a few lines of C.
  • library location: Is relying on default dynamic loader search behavior good for our use case? The libloading docs recommend against it, saying a specific path should be preferred to avoid platform-specific quirks, but this seems wrong to me, since there's a reason different distros and platforms want to say for themselves where libraries should be located, and LD_LIBRARY_PATH works fine for local configurations.
  • CICD tests: I'd rather not include yet another entire test run for each platform, when ostensibly the behavior should be nearly identical to the multicall binary, but not testing at all seems liable to cause breakage. The middle ground I chose is to build and run the dynamically linked multicall binary on the compatible Unixes (seems like Rust doesn't support .so generation with musl yet), to ensure it loads fine, but not run the tests with it. On the other hand, the default test suite is rarely the bottleneck in CICD, so we could run it if we think that overhead is acceptable.
  • naming conventions: Feel free to nit the name of the library, feature, etc.

Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/timeout/timeout (passes in this run but fails in the 'main' branch)

@jtracey
Copy link
Contributor Author

jtracey commented May 13, 2025

Because the diff is deceptively large (git has no internal notion of moved files, so the "move + creating a new file at the old location" looks like two large edits instead), here's the actual main difference, from the output of git diff main:src/bin/coreutils.rs dynamic-multicall:src/lib/coreutils.rs, for simpler reviewing:

diff --git a/src/bin/coreutils.rs b/src/lib/coreutils.rs
index b29e7ea23..68da02af1 100644
--- a/src/bin/coreutils.rs
+++ b/src/lib/coreutils.rs
@@ -20,7 +20,11 @@ const VERSION: &str = env!("CARGO_PKG_VERSION");
 include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
 
 fn usage<T>(utils: &UtilityMap<T>, name: &str) {
-    println!("{name} {VERSION} (multi-call binary)\n");
+    #[cfg(not(feature = "dynamic"))]
+    let kind = "binary";
+    #[cfg(feature = "dynamic")]
+    let kind = "dynamic library";
+    println!("{name} {VERSION} (multi-call {kind})\n");
     println!("Usage: {name} [function [arguments...]]");
     println!("       {name} --list\n");
     println!("Options:");
@@ -51,7 +55,7 @@ fn name(binary_path: &Path) -> Option<&str> {
 }
 
 #[allow(clippy::cognitive_complexity)]
-fn main() {
+pub fn multicall_main() {
     uucore::panic::mute_sigpipe_panic();
 
     let utils = util_map();
@@ -239,3 +243,9 @@ fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command {
     }
     command
 }
+
+#[cfg(feature = "dynamic")]
+#[unsafe(no_mangle)]
+pub extern "Rust" fn coreutils_multicall_main_wrapper() {
+    multicall_main();
+}

The new src/bin/coreutils.rs can then be directly viewed for review.

@sylvestre
Copy link
Contributor

isn't it just a workaround to bypass limitations of apparmor ? :)

@jtracey
Copy link
Contributor Author

jtracey commented May 14, 2025

Right now, there's not a lot of advantage to dynamic linking. This is mainly a stepping stone to individual shim applications that would link against the one shared library. Once that exists, it's not so much a limitation of apparmor that's being addressed as the entire idea of restricting the capabilities of individual utilities. The multicall binary will never be able to do that, since there is no secure mechanism of figuring out how it was invoked (argv[0] is trivial to manipulate).

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
Morty Proxy This is a proxified and sanitized view of the page, visit original site.