Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading a child process's /proc/pid/mem file from the parent

In the program below, I am trying to cause the following to happen:

  1. Process A assigns a value to a stack variable a.
  2. Process A (parent) creates process B (child) with PID child_pid.
  3. Process B calls function func1, passing a pointer to a.
  4. Process B changes the value of variable a through the pointer.
  5. Process B opens its /proc/self/mem file, seeks to the page containing a, and prints the new value of a.
  6. Process A (at the same time) opens /proc/child_pid/mem, seeks to the right page, and prints the new value of a.

The problem is that, in step 6, the parent only sees the old value of a in /proc/child_pid/mem, while the child can indeed see the new value in its /proc/self/mem. Why is this the case? Is there any way that I can get the parent to to see the child's changes to its address space through the /proc filesystem?

#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#define PAGE_SIZE 0x1000
#define LOG_PAGE_SIZE 0xc
#define PAGE_ROUND_DOWN(v) ((v) & (~(PAGE_SIZE - 1)))
#define PAGE_ROUND_UP(v) (((v) + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1)))
#define OFFSET_IN_PAGE(v) ((v) & (PAGE_SIZE - 1))
# if defined ARCH && ARCH == 32
#define BP "ebp"
#define SP "esp"
#else
#define BP "rbp"
#define SP "rsp"
#endif

typedef struct arg_t {
 int a;
} arg_t;


void func1(void * data) {
 arg_t * arg_ptr = (arg_t *)data;
 printf("func1: old value: %d\n", arg_ptr->a);
 arg_ptr->a = 53;
 printf("func1: address: %p\n", &arg_ptr->a);
 printf("func1: new value: %d\n", arg_ptr->a);
}


void expore_proc_mem(void (*fn)(void *), void * data) {

 off_t frame_pointer, stack_start;
 char buffer[PAGE_SIZE];
 const char * path = "/proc/self/mem";
 int child_pid, status;
 int parent_to_child[2];
 int child_to_parent[2];
 arg_t * arg_ptr;
 off_t child_offset;

 asm volatile ("mov %%"BP", %0" : "=m" (frame_pointer));
 stack_start = PAGE_ROUND_DOWN(frame_pointer);

 printf("Stack_start: %lx\n",
        (unsigned long)stack_start);

 arg_ptr = (arg_t *)data;
 child_offset = 
  OFFSET_IN_PAGE((off_t)&arg_ptr->a);
 printf("Address of arg_ptr->a: %p\n",
        &arg_ptr->a);

 pipe(parent_to_child);
 pipe(child_to_parent);
 bool msg;
 int child_mem_fd;
 char child_path[0x20];

 child_pid = fork();
 if (child_pid == -1) {
  perror("fork");
  exit(EXIT_FAILURE);
 }
 if (!child_pid) {
  close(child_to_parent[0]);
  close(parent_to_child[1]);
  printf("CHILD (pid %d, parent pid %d).\n",
         getpid(), getppid());
  fn(data);
  msg = true;
  write(child_to_parent[1], &msg, 1);
  child_mem_fd = open("/proc/self/mem", O_RDONLY);
  if (child_mem_fd == -1) {
   perror("open (child)");
   exit(EXIT_FAILURE);
  }
  printf("CHILD: child_mem_fd: %d\n", child_mem_fd);
  if (lseek(child_mem_fd, stack_start, SEEK_SET) == (off_t)-1) {
   perror("lseek");
   exit(EXIT_FAILURE);
  }

  if (read(child_mem_fd, buffer, sizeof(buffer)) 
      != sizeof(buffer)) {
   perror("read");
   exit(EXIT_FAILURE);
  }

  printf("CHILD: new value %d\n",
         *(int *)(buffer + child_offset));

  read(parent_to_child[0], &msg, 1);
  exit(EXIT_SUCCESS);
 }
 else {
  printf("PARENT (pid %d, child pid %d)\n",
         getpid(), child_pid);
  printf("PARENT: child_offset: %lx\n",
         child_offset);
  read(child_to_parent[0], &msg, 1);
  printf("PARENT: message from child: %d\n", msg);
  snprintf(child_path, 0x20, "/proc/%d/mem", child_pid);
  printf("PARENT: child_path: %s\n", child_path);
  child_mem_fd = open(path, O_RDONLY);
  if (child_mem_fd == -1) {
   perror("open (child)");
   exit(EXIT_FAILURE);
  }
  printf("PARENT: child_mem_fd: %d\n", child_mem_fd);
  if (lseek(child_mem_fd, stack_start, SEEK_SET) == (off_t)-1) {
   perror("lseek");
   exit(EXIT_FAILURE);
  }

  if (read(child_mem_fd, buffer, sizeof(buffer)) 
      != sizeof(buffer)) {
   perror("read");
   exit(EXIT_FAILURE);
  }

  printf("PARENT: new value %d\n",
         *(int *)(buffer + child_offset));

  close(child_mem_fd);

  printf("ENDING CHILD PROCESS.\n");

  write(parent_to_child[1], &msg, 1);
  if (waitpid(child_pid, &status, 0) == -1) {
   perror("waitpid");
   exit(EXIT_FAILURE);
  }
 }

}

int main(void) {

 arg_t arg;
 arg.a = 42;
 printf("In main: address of arg.a: %p\n", &arg.a);
 explore_proc_mem(&func1, &arg.a);

 return EXIT_SUCCESS;
}

This program produces the output below. Notice that the value of a (boldfaced) differs between parent's and child's reading of the /proc/child_pid/mem file.

In main: address of arg.a: 0x7ffffe1964f0
Stack_start: 7ffffe196000
Address of arg_ptr->a: 0x7ffffe1964f0
PARENT (pid 20376, child pid 20377)
PARENT: child_offset: 4f0
CHILD (pid 20377, parent pid 20376).
func1: old value: 42
func1: address: 0x7ffffe1964f0
func1: new value: 53
PARENT: message from child: 1
CHILD: child_mem_fd: 4
PARENT: child_path: /proc/20377/mem
CHILD: new value 53
PARENT: child_mem_fd: 7
PARENT: new value 42
ENDING CHILD PROCESS.

like image 332
Amittai Aviram Avatar asked Nov 28 '25 21:11

Amittai Aviram


1 Answers

There's one silly mistake in this code:

const char * path = "/proc/self/mem";
...
snprintf(child_path, 0x20, "/proc/%d/mem", child_pid);
printf("PARENT: child_path: %s\n", child_path);
child_mem_fd = open(path, O_RDONLY);

So you always end up reading parent's memory here. However after changing this, I get:

CHILD: child_mem_fd: 4
CHILD: new value 53
read (parent): No such process

And I don't know why it could happen - maybe /proc is too slow in refreshing the entries? (it's from perror("read") in the parent - had to add a comment to see which one fails) But that seems weird, since the seek worked - as well as open itself.

That question doesn't seem to be new either: http://lkml.indiana.edu/hypermail/linux/kernel/0007.1/0939.html (ESRCH is "no such process")

Actually a better link is: http://www.webservertalk.com/archive242-2004-7-295131.html - there was an issue with marking processes pthread-attach-safe. You can find there Alan Cox sending someone to Solar Designer... for me that spells "here be dragons" and that it's not solvable if you don't hack kernels in your sleep :(

Maybe it's enough for you to check what is gdb doing in that case and replicating it? (Probably it just goes via ptrace(PTRACE_PEEKDATA,...))

like image 181
viraptor Avatar answered Dec 01 '25 12:12

viraptor