eunomia-bpf / bpftime

Userspace eBPF runtime for fast Uprobe & Syscall hook & Extensions
https://eunomia.dev/bpftime/
MIT License
744 stars 73 forks source link

Unable to Override Symbol `pread` or `read` in '/lib/x86_64-linux-gnu/libc.so.6' Using Uprobe #310

Open fr0m-scratch opened 1 month ago

fr0m-scratch commented 1 month ago

I was unable to override symbol pread or read in '/lib/x86_64-linux-gnu/libc.so.6'. It works perfectly fine for write, both using bpf_override_return to override the return value of the client program and replacing the original write syscall by returning non-zero value in bpf program. However, override does not work for either pread or read.

Both related configs were set as

CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_FUNCTION_ERROR_INJECTION=y

Initially, I thought the problem is related to the global symbols. However, override does not with the weak symbol pread either.

symbols

Part of my BPF program:

SEC("uprobe/libc.so.6:write")
int bpf_write_patch(struct pt_regs *ctx) {
  int fd = PT_REGS_PARM1(ctx);
  const void *buf = (const void *)PT_REGS_PARM2(ctx);
  size_t n = PT_REGS_PARM3(ctx);

  if (!global || fd < 3) {
    return 0;
  }

  struct uring_check_data op = {.pid = bpf_get_current_pid_tgid() >> 32,
                                .fd = fd,
                                .i_ino = (__u64)buf,
                                .depends_on = 0,
                                .res = 0};

  bpf_printk("write called with fd: %d\n, content: %s\n", fd, buf);
  int ret = io_uring_submit_write(fd, buf, n);

  io_uring_set_linkflag();
  // check for stale data
  struct uring_check_data *op_stale = add_pending_op(&op);
  bpf_printk("res: %d\n", op_stale->res);
  bpf_override_return(ctx, 3200);
  return -1;
}

SEC("uprobe/libc.so.6:read")
int bpf_read_patch(struct pt_regs *ctx) {
  int fd = PT_REGS_PARM1(ctx);
  void *buf = (void *)PT_REGS_PARM2(ctx);
  size_t n = PT_REGS_PARM3(ctx);

  if (!global || fd < 3) {
    return 0;
  }
  bpf_printk("read called with fd: %d\n, content: %s\n", fd, buf);
  struct uring_check_data op = {.pid = bpf_get_current_pid_tgid() >> 32,
                                .fd = fd,
                                .i_ino = (__u64)buf,
                                .depends_on = 0,
                                .res = 0};

  int ret = io_uring_submit_read(fd, buf, n, -1);

  io_uring_set_linkflag();
  struct uring_check_data *op_stale = add_pending_op(&op);

  // bpf_printk("res: %d\n", op_stale->res);

  int res = bpf_override_return(ctx, 22);
  bpf_printk("res: %d\n", res);
  return 1;
}

SEC("uprobe/libc.so.6:pread")
int bpf_pread_patch(struct pt_regs *ctx) {
  int fd = PT_REGS_PARM1(ctx);
  void *buf = (void *)PT_REGS_PARM2(ctx);
  size_t n = PT_REGS_PARM3(ctx);
  off_t offset = PT_REGS_PARM4(ctx);

  if (!global || fd < 3) {
    return 0;
  }
  bpf_printk("pread called with fd: %d\n, content: %s\n", fd, buf);
  struct uring_check_data op = {.pid = bpf_get_current_pid_tgid() >> 32,
                                .fd = fd,
                                .i_ino = (__u64)buf,
                                .depends_on = 0,
                                .res = 0};

  int ret = io_uring_submit_pread(fd, buf, n, offset);
  if (ret) {
    return 0;
  }

  io_uring_set_linkflag();
  add_pending_op(&op);

  bpf_override_return(ctx, 22);
  return 1;
}

Part of my user-space code:

    err = bpf_prog_attach_uprobe_with_override(bpf_program__fd(obj->progs.bpf_write_patch),
                                               "/lib/x86_64-linux-gnu/libc.so.6", "write");
    if (err) {
        fprintf(stderr, "Failed to attach BPF program to write\n");
        goto cleanup;
    }

    err = bpf_prog_attach_uprobe_with_override(bpf_program__fd(obj->progs.bpf_read_patch),
                                               "/lib/x86_64-linux-gnu/libc.so.6", "read");
    if (err) {
        fprintf(stderr, "Failed to attach BPF program to read\n");
        goto cleanup;
    }

    err = bpf_prog_attach_uprobe_with_override(bpf_program__fd(obj->progs.bpf_pread_patch),
                                               "/lib/x86_64-linux-gnu/libc.so.6", "pread");
    if (err) {
        fprintf(stderr, "Failed to attach BPF program to pread\n");
        goto cleanup;
    }

Any help and suggestion is appreciated! Thanks!

Officeyutong commented 1 month ago

Could you please check if pread and read was actually hooked (i.e hooks will be triggered when these functions were called)

fr0m-scratch commented 1 month ago

Thank you for your help! I think read and pread are indeed hooked.

I modify the bpf program to be as simple as

SEC("uprobe/libc.so.6:write")
int bpf_write_patch(struct pt_regs *ctx) {

  bpf_printk("write called\n");
  bpf_override_return(ctx, 9999);
  return 0;

}

SEC("uprobe/libc.so.6:read")
int bpf_read_patch(struct pt_regs *ctx) {

  bpf_printk("read called\n");
  bpf_override_return(ctx, 9999);
  return 0;
}

And victim.c to be

int main() {
  int sourceFile, destFile;
  char buffer[BUFFER_SIZE];
  int bytesRead, bytesWritten;

  sourceFile = open("./1mb.txt", O_RDONLY);
  if (sourceFile == -1) {
    perror("Error opening source file");
    return EXIT_FAILURE;
  }

  destFile =
      open("./temp.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
  if (destFile == -1) {
    perror("Error opening destination file");
    return EXIT_FAILURE;
  }

  clock_t start = clock();
  int count = 0;

  struct stat file_stat;
  if (fstat(sourceFile, &file_stat) == -1) {
    perror("Error getting file stats");
    return -1;
  }
  int remaining_bytes = file_stat.st_size;
  printf("File size: %d\n", remaining_bytes);
  while (remaining_bytes > 0) {
    bytesRead = BUFFER_SIZE;
    if (remaining_bytes < BUFFER_SIZE) {
      bytesRead = remaining_bytes;
    }
    bytesRead = read(sourceFile, buffer, bytesRead);
    printf("Bytes read: %d\n", bytesRead);

    remaining_bytes -= bytesRead;

    bytesWritten = write(destFile, buffer, bytesRead);
    printf("Bytes written: %d\n", bytesWritten);

    if (bytesWritten == -1) {
      perror("Error writing to destination file");
      return EXIT_FAILURE;
    }
    count++;
  }

  clock_t end = clock();

  close(destFile);
  close(sourceFile);
  printf("File copied successfully in %f seconds\n",
         (double)(end - start) / CLOCKS_PER_SEC);

  return EXIT_SUCCESS;
}

The output is shown below, where write is successfully overridden and read is not:

Screenshot 2024-07-18 at 8 46 51 PM

Same applies to pread as well

Screenshot 2024-07-18 at 8 53 05 PM

Additionally, when I added a wrapper to read, everything works fine, so I am just curious of the cause to this problem.

Officeyutong commented 1 month ago

Thank you for your help! I think read and pread are indeed hooked.

I modify the bpf program to be as simple as

SEC("uprobe/libc.so.6:write")
int bpf_write_patch(struct pt_regs *ctx) {

  bpf_printk("write called\n");
  bpf_override_return(ctx, 9999);
  return 0;

}

SEC("uprobe/libc.so.6:read")
int bpf_read_patch(struct pt_regs *ctx) {

  bpf_printk("read called\n");
  bpf_override_return(ctx, 9999);
  return 0;
}

And victim.c to be

int main() {
  int sourceFile, destFile;
  char buffer[BUFFER_SIZE];
  int bytesRead, bytesWritten;

  sourceFile = open("./1mb.txt", O_RDONLY);
  if (sourceFile == -1) {
    perror("Error opening source file");
    return EXIT_FAILURE;
  }

  destFile =
      open("./temp.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
  if (destFile == -1) {
    perror("Error opening destination file");
    return EXIT_FAILURE;
  }

  clock_t start = clock();
  int count = 0;

  struct stat file_stat;
  if (fstat(sourceFile, &file_stat) == -1) {
    perror("Error getting file stats");
    return -1;
  }
  int remaining_bytes = file_stat.st_size;
  printf("File size: %d\n", remaining_bytes);
  while (remaining_bytes > 0) {
    bytesRead = BUFFER_SIZE;
    if (remaining_bytes < BUFFER_SIZE) {
      bytesRead = remaining_bytes;
    }
    bytesRead = read(sourceFile, buffer, bytesRead);
    printf("Bytes read: %d\n", bytesRead);

    remaining_bytes -= bytesRead;

    bytesWritten = write(destFile, buffer, bytesRead);
    printf("Bytes written: %d\n", bytesWritten);

    if (bytesWritten == -1) {
      perror("Error writing to destination file");
      return EXIT_FAILURE;
    }
    count++;
  }

  clock_t end = clock();

  close(destFile);
  close(sourceFile);
  printf("File copied successfully in %f seconds\n",
         (double)(end - start) / CLOCKS_PER_SEC);

  return EXIT_SUCCESS;
}

The output is shown below, where write is successfully overridden and read is not: Screenshot 2024-07-18 at 8 46 51 PM Same applies to pread as well Screenshot 2024-07-18 at 8 53 05 PM

Additionally, when I added a wrapper to read, everything works fine, so I am just curious of the cause to this problem.

Thanks for your reply! I'll investigate into it and try to reproduce it first

Officeyutong commented 1 month ago

I'm not able to reproduce it on my machine..Could please help me create a docker image to reproduce it? (at least overriding to read works)