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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions