Trouble using C Variadic function to define and override libc printf using ld_preload_library

I am trying to build a LD_PRELOAD library in Rust that can override printf in libc
I am using rust nightly, which is what supports c_variadic for now, as I understand it

bash-4.4$ rustc --version
rustc 1.47.0-nightly (7e6d6e5f5 2020-08-16)
#![feature(c_variadic)]

#[no_mangle]
pub unsafe extern "C" fn vprintf (format: *const c_char, mut _args: std::ffi::VaList)  -> c_int {
    println!("vprintf -> Intercept");
    0
}

#[no_mangle]
pub unsafe extern "C" fn printf (format: *const c_char, mut _args: ...)  -> c_int {
    println!("printf --> intercept");
    0
}

My test program is pretty simple

#include <stdio.h>
#include <stdarg.h>

int testprintf(const char *format, ...)
{
    va_list argp;

    va_start(argp, format);
    vprintf(format, argp);
    va_end(argp);

}

int main(int argc, char *argv)
{
    testprintf("vprintf: Hello World");
    printf("printf: Hello World\n");

}

The printf doesnt intercepts.
But the vprintf does intercept.

Am I doing the C_variadic function wrong?

+ LD_PRELOAD=target/debug/libvarprintspy.so
+ ./testprog
vprintf -> Intercept
printf: Hello World

Are you compiling the C code with optimisations? It could be that the optimiser "saw through" your use of printf() and lowered it to a call to puts(), avoiding your printf() entirely.

Try calling the variadic function something else (e.g. int sum(...)) and see if that changes things.

Tried that, not seeing a difference

#[no_mangle]
pub unsafe extern "C" fn vprintf (_format: *const c_char, mut _args: std::ffi::VaList)  -> c_int {
    println!("vprintf -> Intercept");
    0
}


#[no_mangle]
pub unsafe extern "C" fn sum (_format: *const c_char, mut _args: ...)  -> c_int {
    println!("printf --> intercept");
    0
}
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>

int testprintf(const char *format, ...)
{
    va_list argp;

    va_start(argp, format);
    vprintf(format, argp);
    va_end(argp);

}

int sum(const char *format, ...)
{
    printf("Original");
    return 0;
}


int main(int argc, char *argv)
{
    char buf[500];
    testprintf("vprintf: Hello World");
    sum("Hello World\n");
    readlink("/ws/sarvi-sjc/testlink", buf, 499);

}

Results:

+ ./testprog
vprintf -> Intercept
Original

Does some one have a working example of overriding c_variadic printf that works to overrride printf in an LD_preload scenario ?

I got it to work just fine on my laptop.

Source code for all files
// override.rs

#![feature(c_variadic)]

use std::{
    os::raw::{c_char, c_int},
    ffi::CStr,
};

#[no_mangle]
pub unsafe extern "C" fn printf(fmt: *const c_char, ...) -> c_int {
    let message = CStr::from_ptr(fmt).to_string_lossy();
    eprintln!("Rust: {}", message);

    0
}
// override.c

int puts(const char *);

int printf(const char *__restrict __fmt, ...)
{
    puts("C: ");
    return puts(__fmt);
}
// main.c

#include <stdio.h>

int main()
{
    printf("Hello, World!");

    return 0;
}
# Makefile

rust: main override_rs.so
	LD_PRELOAD=$$(pwd)/override_rs.so ./main

c: main override_c.so
	LD_PRELOAD=$$(pwd)/override_c.so ./main

override_rs.so: override.rs
	rustc --crate-type=cdylib override.rs -o override_rs.so

override_c.so: override.c
	$(CC) --shared override.c -o override_c.so

main: main.c
	$(CC) main.c -o main.c

.PHONY: rust c

Here are the commands I used:

$ rustc --version --verbose
rustc 1.47.0-nightly (7e6d6e5f5 2020-08-16)
binary: rustc
commit-hash: 7e6d6e5f535321c2223f044caba16f97b825009c
commit-date: 2020-08-16
host: x86_64-unknown-linux-gnu
release: 1.47.0-nightly
LLVM version: 10.0

$ clang --version
clang version 10.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

$ make main
cc main.c -o main
$ ./main
Hello, World!

$ make c
cc --shared override.c -o override_c.so
LD_PRELOAD=$(pwd)/override_c.so ./main
C:
Hello, World!

$ make rust
rustc --crate-type=cdylib override.rs -o override_rs.so
LD_PRELOAD=$(pwd)/override_rs.so ./main
Rust: Hello, World!

Hang on... Are these the exact commands you are using, where + is your shell's prompt? If so the problem has nothing to do with Rust. You just aren't telling your shell (I'm guessing bash or zsh?) to set the environment variable correctly.

If you want a variable to be available across invocations you need to export it. Using the KEY=VALUE syntax will only apply the environment variable for the current command, so you should be invoking LD_PRELOAD=target/debug/libvarprintspy.so ./testprog (note that they're both done in a single command).

1 Like

Note that in your sum example you call a printf that has no other option but to come from C.

Try doing:

#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>

int sum (char const * format, ...);

int test_vprintf (const char *format, ...)
{
    va_list argp;

    va_start(argp, format);
    vprintf(format, argp); // dl lookup to be intercepted by Rust's `.so`
    va_end(argp);

}

int test_printf (const char *format, ...)
{
    sum("Original");  // dl lookup to be intercepted by Rust's `.so`
    return 0;
}


int main (int argc, char const * const argv[])
{
    test_vprintf("vprintf: Hello World");
    test_printf("printf: Hello World\n");
}

Thanks the problem looks like with main program.
Your main program works and mine obviously didn't.

So I started tweaking my program down to yours and found something wierd, atleast to me.
I am sure I am missing something.

Here is what works

#include <stdio.h>

int main()
{
    printf("Hello World!");

    return 0;
}

Here is what doesn't work

#include <stdio.h>

int main()
{
    printf("Hello World!\n");

    return 0;
}

The difference is the "\n" at the the end of "Hello World\n".

I don't understand why that difference. I suspect it might have something to with optimizations. But not sure.

Looks that is what is happenning. gcc optimizes printf to puts in some cases as here

Yep, printf("some_literal_string\n") can and does get optimized to puts("some_literal_string");

As suggested above, you should try using a symbol different from printf:

  1. printf_shadow.h

    #ifndef __PRINTF_SHADOW__H__
    #define __PRINTF_SHADOW__H__
    
    #define printf __my_shadowed_printf__
    
    int printf (char const * fmt, ...);
    
    #endif /* __PRINTF_SHADOW__H__ */
    
  2. and then #include "printf_shadow.h" to make the C code in that file use __my_shadowed_printf__.

  3. Finally, have the Rust code define a function with that name, e.g., by using #[export_name = "..."]:

    #[export_name = "__my_shadowed_printf__"] pub // exported
    unsafe extern "C" // C ABI
    fn printf (fmt: *const c_char, ...) -> c_int
    {
        ...
    }
    

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.