Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
fr0m-scratch opened this issue Jul 13, 2024 · 4 comments

Comments

@fr0m-scratch
Copy link
Contributor

fr0m-scratch commented Jul 13, 2024

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
Copy link
Contributor

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

@fr0m-scratch
Copy link
Contributor Author

fr0m-scratch commented Jul 19, 2024

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
Copy link
Contributor

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
Copy link
Contributor

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants