eunomia-bpf / bpftime

Userspace eBPF runtime for Observability, Network & General Extensions Framework
https://eunomia.dev/bpftime/
MIT License
789 stars 74 forks source link

Failed to override return value using `bpf_override_return` #213

Open gouravkrosx opened 8 months ago

gouravkrosx commented 8 months ago

I'm experimenting with overriding the Python time function and attempted to use Cilium eBPF but faced challenges, leading me to explore this library. Below is my eBPF code:

/* SPDX-License-Identifier: MIT
 *
 * Copyright (c) 2022, eunomia-bpf org
 * All rights reserved.
 */
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

SEC("uprobe/_PyTime_GetSystemClock")
int _PyTime_GetSystemClock_bpf(struct pt_regs *ctx)
{
    u64 id = bpf_get_current_pid_tgid();
    u32 pid = id >> 32;
    if (pid != 1612255 && pid != 1612261)
    {
        return 0;
    }

    u64 time = (PT_REGS_RC(ctx));

    bpf_printk("[_PyTime_GetSystemClock_bpf]Info: called :%lu & time is:%llu", pid, time);
    u64 t = 1706533301399118410; // Some time value to overwrite
    long ret = bpf_override_return(ctx, t);
    if (ret != 0)
    {
        bpf_printk("[_PyTime_GetSystemClock_bpf]Error: bpf_override_return failed");
        return 0;
    }

    // Print the process pid
    bpf_printk("[_PyTime_GetSystemClock_bpf]Info: Modified time for pid: %lu & time is:%llu", pid, t);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";

And here's the userspace code:

#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include <bpf/bpf.h>
#include <unistd.h>
#include <stdlib.h>
#include "time_freezer.skel.h"
#include <inttypes.h>
#include "attach_override.h"

#define warn(...) fprintf(stderr, __VA_ARGS__)

static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
               va_list args)
{
    return vfprintf(stderr, format, args);
}

static volatile bool exiting = false;

static void sig_handler(int sig)
{
    exiting = true;
}

int main(int argc, char **argv)
{
    struct time_freezer_bpf *skel;
    int err;

    /* Set up libbpf errors and debug info callback */
    libbpf_set_print(libbpf_print_fn);

    /* Cleaner handling of Ctrl-C */
    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

    /* Load and verify BPF application */
    skel = time_freezer_bpf__open();
    if (!skel) {
        fprintf(stderr, "Failed to open and load BPF skeleton\n");
        return 1;
    }

    /* Load & verify BPF programs */
    err = time_freezer_bpf__load(skel);
    if (err) {
        fprintf(stderr, "Failed to load and verify BPF skeleton\n");
        goto cleanup;
    }
    err = bpf_prog_attach_uprobe_with_override(
        bpf_program__fd(skel->progs._PyTime_GetSystemClock_bpf), "/usr/bin/python3.10",
        "_PyTime_GetSystemClock");
    if (err) {
        fprintf(stderr, "Failed to attach BPF program\n");
        goto cleanup;
    }
    while (!exiting) {
        sleep(1);
    }
cleanup:
    /* Clean up */
    time_freezer_bpf__destroy(skel);
    return err < 0 ? -err : 0;
}

The program is run using the following command: LD_PRELOAD=~/.bpftime/libbpftime-syscall-server.so ./time_freezer It loads without any errors.

I'm quite new to this library and unsure about what I might be doing incorrectly. Could you please provide some guidance?

The Python handler where I attempt to get the time is as follows:

@app.route('/time', methods=['GET'])  # New route to get current time
def get_current_time():
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return jsonify({'current_time': current_time})
yunwei37 commented 8 months ago

How have you run the Python program with something like LD_PRELOAD=~/.bpftime/libbpftime-agent.so [Your python program]? What's the output?

See https://github.com/eunomia-bpf/bpftime/tree/master/example/minimal

You can load the LD_PRELOAD=~/.bpftime/libbpftime-syscall-server.so ./time_freezer first, and execute the LD_PRELOAD=~/.bpftime/libbpftime-agent.so [Your python program] in another shell.

yunwei37 commented 8 months ago

image

It can work in my computer. See my experiment code in https://github.com/eunomia-bpf/bpftime/blob/uprobe-python/example/minimal/uprobe-override.bpf.c

yunwei37 commented 8 months ago

If you find any documents making you confused, please tell us or open a PR : )

gouravkrosx commented 8 months ago

@yunwei37 Thank you very much for your help, the solution you provided worked perfectly! It turns out I had overlooked starting the client with the command you provided, which was my mistake.

I have two questions that I'm hoping you can help with:

  1. Does a Go-based loader exist for loading and attaching eBPF programs using the bpftime library, similar to what Cilium offers? I'm looking for a way to manage my eBPF programs more efficiently within a Go environment.

  2. How to build binaries in such a way that targeted function symbols are exposed?

yunwei37 commented 8 months ago

Thanks!

For the first question, maybe you can try https://github.com/aquasecurity/libbpfgo, it should be able to work with bpftime.

For the second questions:

To build binaries with targeted function symbols exposed, you generally need to follow a few key steps. This process involves configuring your build system and compiler flags to ensure that the symbols you want are not stripped during the compilation and linking process. Here's a general approach:

1. Use Compiler Flags

2. Control Symbol Visibility

3. Configure Linker

Ensure that the linker does not strip away the symbols you want to keep.

4. Use Static Libraries Carefully

If you are linking against static libraries, ensure that the symbols you need are not removed during the linkage. Sometimes, using shared libraries (.so or .dll) might be more straightforward for ensuring symbol visibility across binaries.

5. Verify Symbol Visibility

After building, you can use tools like nm (on Unix-like systems) or dumpbin (on Windows) to verify that your symbols are indeed exposed in the binary.

This approach ensures that your binary exposes the necessary function symbols while maintaining the rest of your codebase as intended. Remember to carefully manage symbol visibility, as exposing too many symbols can lead to larger binary sizes and potentially more complex dependency issues.

From GPT4, seems it's mostly correct

gouravkrosx commented 8 months ago

Thanks again for your help.

Could you share an example of using libbpfgo with bpftime? Also, I'm curious if bpftime is compatible with cilium since they seem similar.

On another note, I'm facing difficulties using bpf_override_return with kernel uretprobes. Any insights on this would be greatly appreciated.

I can see in this article that it is not supported I guess. Any reason why?

On the other hand, Uprobe is currently limited to tracing and cannot modify the execution flow or return values of user-space functions