Skip to content

libafl_qemu_build partial linking fails because quoted --dynamic-list argument is passed literally to ld #3827

Description

@sillyZAI

IMPORTANT

  1. I have verified that the issue is present in the current main branch.

Thank you for making LibAFL better!

Describe the bug

When building fuzzers/full_system/qemu_baremetal, the QEMU bridge itself appears to build successfully, but libafl_qemu_build fails during the partial linking step.

The failure happens in:

crates/libafl_qemu/libafl_qemu_build/src/build.rs

around the code that invokes the C++ compiler with -r to generate:

libqemu-partially-linked.o

The generated link.command contains a linker argument that is shell-quoted with single quotes:

-Xlinker "'--dynamic-list=/home/xilizai/LibAFL/fuzzers/full_system/qemu_baremetal/target/debug/qemu-libafl-bridge/build/plugins/qemu-plugin.symbols'"

Since Rust Command::args() does not invoke a shell, the single quotes are passed literally to ld.

As a result, ld tries to find a file named:

'--dynamic-list=/path/to/qemu-plugin.symbols'

instead of treating it as the linker option:

--dynamic-list=/path/to/qemu-plugin.symbols

The relevant part of build.rs is:

let mut link_command = cpp_compiler.to_command();

link_command
    .current_dir(&libafl_qemu_build_dir)
    .arg("-o")
    .arg("libqemu-partially-linked.o")
    .arg("-r")
    .args(cmd);

Because Command::args() does not perform shell parsing, the quoted linker argument is passed to the linker unchanged.

My fuzzer's Cargo.toml is the one from:

fuzzers/full_system/qemu_baremetal/Cargo.toml

The build was performed with the default arm target feature.

Environment:

OS: Ubuntu Linux
Host: x86_64
Rust toolchain: stable-x86_64-unknown-linux-gnu
Example: fuzzers/full_system/qemu_baremetal
Target feature: arm
qemu-libafl-bridge commit: c9c6db9127509e1eeaf10daad8fa6bc12cc54f56
qemu-libafl-bridge commit message: Merge pull request #123 from rmalmain/qemu_update_v10_2_0
LibAFL commit: <please fill in the output of `git rev-parse HEAD`>

To Reproduce

Steps to reproduce the behavior:

  1. Clone LibAFL and checkout the current main branch.

  2. Go to the baremetal QEMU fuzzer example:

cd fuzzers/full_system/qemu_baremetal
  1. Build with verbose output:
RUST_BACKTRACE=1 CARGO_TERM_VERBOSE=true cargo build -vv
  1. The build fails during the libafl_qemu_build partial linking step with:
thread 'main' panicked at crates/libafl_qemu/libafl_qemu_build/src/build.rs:526:13:
Linking failed.
  1. Inspect the generated files in the QEMU bridge build directory:
cd target/debug/qemu-libafl-bridge/build
cat link.command
cat link.stderr

Expected behavior

The partial linking step should pass the linker argument without literal shell quotes.

Expected form:

-Xlinker --dynamic-list=/path/to/qemu-plugin.symbols

or the build script should sanitize shell-quoted arguments before passing them to Command::args().

The build should finish successfully and generate the qemu_baremetal binary.

Screen output/Screenshots

The build fails with:

thread 'main' panicked at crates/libafl_qemu/libafl_qemu_build/src/build.rs:526:13:
Linking failed.

The generated link.stderr contains:

/usr/bin/ld: cannot find '--dynamic-list=/home/xilizai/LibAFL/fuzzers/full_system/qemu_baremetal/target/debug/qemu-libafl-bridge/build/plugins/qemu-plugin.symbols': No such file or directory
collect2: error: ld returned 1 exit status

The generated link.command contains:

-Xlinker "'--dynamic-list=/home/xilizai/LibAFL/fuzzers/full_system/qemu_baremetal/target/debug/qemu-libafl-bridge/build/plugins/qemu-plugin.symbols'"

Running ninja -v manually inside the generated QEMU build directory does not report an error:

cd target/debug/qemu-libafl-bridge/build
ninja -v

The QEMU bridge artifacts are generated successfully, for example:

libqemu-system-arm.so
libqemuutil.a
subprojects/dtc/libfdt/libfdt.a

The failure happens later in the libafl_qemu_build partial linking step.

Additional context

This does not seem to be a missing dependency issue. The QEMU/Meson/Ninja build itself succeeds. The failure happens when libafl_qemu_build performs an additional partial link step to generate:

libqemu-partially-linked.o

I was able to build successfully by stripping surrounding quotes before passing arguments to Command::args().

For example:

fn unquote_link_arg(arg: String) -> String {
    if arg.len() >= 2 {
        let bytes = arg.as_bytes();
        let first = bytes[0];
        let last = bytes[bytes.len() - 1];

        if (first == b'\'' && last == b'\'') || (first == b'"' && last == b'"') {
            return arg[1..arg.len() - 1].to_string();
        }
    }

    arg
}

and then:

let fixed_cmd: Vec<String> = cmd.into_iter().map(unquote_link_arg).collect();

link_command
    .current_dir(&libafl_qemu_build_dir)
    .arg("-o")
    .arg("libqemu-partially-linked.o")
    .arg("-r")
    .args(fixed_cmd);

After this local change, the build succeeds:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 46s

I am happy to submit a PR if this approach looks acceptable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions