Skip to content

Instantly share code, notes, and snippets.

@bjackman
Created March 31, 2026 13:40
Show Gist options
  • Select an option

  • Save bjackman/131069a6de9adb66df7ca8e12ea2a196 to your computer and use it in GitHub Desktop.

Select an option

Save bjackman/131069a6de9adb66df7ca8e12ea2a196 to your computer and use it in GitHub Desktop.
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <stdbool.h>
#include <sched.h>
#include <sys/syscall.h>
#define STACK_SIZE (1024 * 1024)
#ifndef SYS_memfd_secret
#define SYS_memfd_secret 447
#endif
struct child_args {
int write_pipe_fd;
bool use_memfd;
bool use_memfd_secret;
};
static int child_func(void *arg) {
struct child_args *args = (struct child_args *)arg;
int pipe_fd = args->write_pipe_fd;
bool use_memfd = args->use_memfd;
bool use_memfd_secret = args->use_memfd_secret;
const char *message = "Hello from the child's memory!";
size_t msg_len = strlen(message) + 1;
void *msg_ptr;
char stack_message[64]; // Declare it here so it's in scope for the whole child life
if (use_memfd_secret) {
int fd = syscall(SYS_memfd_secret, 0);
if (fd == -1) {
perror("[Child] memfd_secret");
if (errno == ENOSYS) {
fprintf(stderr, "[Child] memfd_secret not supported by kernel.\n");
} else if (errno == ENOTTY) {
fprintf(stderr, "[Child] memfd_secret not enabled in kernel (secretmem_enable=1 boot param?).\n");
}
return EXIT_FAILURE;
}
if (ftruncate(fd, msg_len) == -1) {
perror("[Child] ftruncate");
return EXIT_FAILURE;
}
msg_ptr = mmap(NULL, msg_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (msg_ptr == MAP_FAILED) {
perror("[Child] mmap");
return EXIT_FAILURE;
}
memcpy(msg_ptr, message, msg_len);
printf("[Child] Using memfd_secret-backed memory at address: %p\n", msg_ptr);
close(fd);
} else if (use_memfd) {
int fd = memfd_create("demo_memfd", 0);
if (fd == -1) {
perror("[Child] memfd_create");
return EXIT_FAILURE;
}
if (ftruncate(fd, msg_len) == -1) {
perror("[Child] ftruncate");
return EXIT_FAILURE;
}
msg_ptr = mmap(NULL, msg_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (msg_ptr == MAP_FAILED) {
perror("[Child] mmap");
return EXIT_FAILURE;
}
memcpy(msg_ptr, message, msg_len);
printf("[Child] Using memfd-backed memory at address: %p\n", msg_ptr);
close(fd);
} else {
strcpy(stack_message, message);
msg_ptr = stack_message;
printf("[Child] Using stack-allocated memory at address: %p\n", msg_ptr);
}
printf("[Child] My PID is %d\n", getpid());
printf("[Child] Message content: %s\n", message);
// Send the address of the message to the parent
if (write(pipe_fd, &msg_ptr, sizeof(void *)) == -1) {
perror("[Child] write");
return EXIT_FAILURE;
}
// Keep the child alive long enough for the parent to read its memory
sleep(2);
printf("[Child] Exiting...\n");
close(pipe_fd);
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
bool use_memfd = false;
bool use_memfd_secret = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--memfd") == 0) {
use_memfd = true;
} else if (strcmp(argv[i], "--memfd_secret") == 0) {
use_memfd_secret = true;
}
}
int pipefd[2];
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// Allocate stack for the cloned child process
char *stack = malloc(STACK_SIZE);
if (!stack) {
perror("malloc");
exit(EXIT_FAILURE);
}
// clone() requires the top of the stack
char *stackTop = stack + STACK_SIZE;
struct child_args cargs;
cargs.write_pipe_fd = pipefd[1];
cargs.use_memfd = use_memfd;
cargs.use_memfd_secret = use_memfd_secret;
pid_t pid = clone(child_func, stackTop, SIGCHLD, &cargs);
if (pid == -1) {
perror("clone");
free(stack);
exit(EXIT_FAILURE);
}
// Parent process
close(pipefd[1]); // Close unused write end
void *remote_address;
char local_buffer[64];
memset(local_buffer, 0, sizeof(local_buffer));
// Read the address from the child
ssize_t read_bytes = read(pipefd[0], &remote_address, sizeof(void *));
if (read_bytes <= 0) {
if (read_bytes == 0) {
fprintf(stderr, "[Parent] Pipe closed by child (child probably failed).\n");
} else {
perror("[Parent] read from pipe");
}
wait(NULL);
free(stack);
exit(EXIT_FAILURE);
}
printf("[Parent] Child PID: %d\n", pid);
printf("[Parent] Remote address to read: %p\n", remote_address);
struct iovec local[1];
local[0].iov_base = local_buffer;
local[0].iov_len = sizeof(local_buffer) - 1;
struct iovec remote[1];
remote[0].iov_base = remote_address;
remote[0].iov_len = sizeof(local_buffer) - 1;
// Use process_vm_readv to read from the child's address space
ssize_t nread = process_vm_readv(pid, local, 1, remote, 1, 0);
if (nread == -1) {
perror("[Parent] process_vm_readv");
wait(NULL);
free(stack);
exit(EXIT_FAILURE);
}
printf("[Parent] Successfully read %zd bytes from child\n", nread);
printf("[Parent] Data read: \"%s\"\n", local_buffer);
close(pipefd[0]);
wait(NULL); // Wait for child to finish
printf("[Parent] Child has exited. Parent exiting.\n");
free(stack);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment