1
0
Fork 0
mirror of https://gitlab.com/niansa/execontrol.git synced 2025-03-06 20:48:26 +01:00

Added whitelist example

This commit is contained in:
niansa/tuxifan 2022-05-21 00:06:55 +02:00
parent 3b5f5d23dc
commit 2d598e2924
4 changed files with 248 additions and 57 deletions

View file

@ -3,4 +3,5 @@ cmake_minimum_required(VERSION 3.5)
project(execontrol LANGUAGES C)
set(CMAKE_C_STANDARD 11)
add_executable(execontrol main.c)
add_executable(execontrol common.h main.c)
add_executable(execontrol_test_whitelist common.h test_whitelist.c)

35
common.h Normal file
View file

@ -0,0 +1,35 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define do_checked(name, ...) check_ret(#name "(" #__VA_ARGS__ ")", name(__VA_ARGS__))
static inline long check_ret(const char *fnc, long value) {
if (value < 0) {
perror(fnc);
exit(-errno);
}
return value;
}
enum {
EXECONTROL_CAN_EXEC
};
enum {
EXECONTROL_UNSPECIFIED_ERROR = 0,
EXECONTROL_OKAY = 1
};
typedef unsigned char execontrol_cmd_t;
typedef unsigned execontrol_pid_t;
typedef unsigned char execontrol_errno_t;
struct execontrol_request {
execontrol_pid_t pid;
execontrol_cmd_t cmd;
};
struct execontrol_response {
execontrol_errno_t error;
};

124
main.c
View file

@ -1,3 +1,5 @@
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -13,19 +15,9 @@
#include <sys/syscall.h>
#include <linux/limits.h>
#define run_checked(name, ...) check_ret(#name "(" #__VA_ARGS__ ")", name(__VA_ARGS__))
long check_ret(const char *fnc, long value) {
if (value < 0) {
perror(fnc);
exit(-errno);
}
return value;
}
char *get_pid_rel_path(int pid, const char *path, size_t path_len) {
char *get_pid_rel_path(execontrol_pid_t pid, const char *path, size_t path_len) {
char *pid_rel_path = malloc(sizeof("/proc/99999999/cwd/")+path_len);
size_t pid_rel_path_len = sprintf(pid_rel_path, "/proc/%d/cwd/", pid);
memcpy(pid_rel_path+pid_rel_path_len, path, path_len);
@ -36,14 +28,32 @@ int is_rel_path(const char *path) {
return path[0] == '.';
}
char check_executable(const char *filename, size_t filename_len, int *control_pipe) {
run_checked(write, control_pipe[1], filename, filename_len);
char fres;
run_checked(read, control_pipe[0], &fres, sizeof(fres));
return fres;
struct execontrol_response can_file(execontrol_cmd_t cmd, execontrol_pid_t pid, const char *filename, size_t filename_len, int *control_pipe) {
struct execontrol_request request = {
.pid = pid,
.cmd = cmd
};
do_checked(write, control_pipe[1], &request, sizeof(request));
do_checked(write, control_pipe[1], filename, filename_len+1);
struct execontrol_response response;
do_checked(read, control_pipe[0], &response, sizeof(response));
return response;
}
size_t read_string(char *buffer, size_t buffer_len, int pid, unsigned long long addr) {
struct execontrol_response can_file_maybe_relative(execontrol_cmd_t cmd, execontrol_pid_t pid, const char *filename, size_t filename_len, int *control_pipe) {
struct execontrol_response response;
if (is_rel_path(filename)) {
char *absolute_path = get_pid_rel_path(pid, filename, filename_len);
response = can_file(cmd, pid, absolute_path, strlen(absolute_path), control_pipe);
free(absolute_path);
} else {
response = can_file(cmd, pid, filename, filename_len, control_pipe);
}
return response;
}
size_t read_string(char *buffer, size_t buffer_len, execontrol_pid_t pid, unsigned long long addr) {
// Read string
size_t string_len;
for (string_len = 0; string_len != buffer_len-1; string_len++) {
@ -60,20 +70,20 @@ size_t read_string(char *buffer, size_t buffer_len, int pid, unsigned long long
return string_len;
}
void run(int pid, int *sync, int *control_pipe) {
void run(execontrol_pid_t pid, int *sync, int *control_pipe) {
// Start ptrace
run_checked(ptrace, PTRACE_SEIZE, pid, 0, 0);
run_checked(ptrace, PTRACE_INTERRUPT, pid, 0, 0);
run_checked(waitpid, pid, 0, 0);
run_checked(ptrace, PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL);
do_checked(ptrace, PTRACE_SEIZE, pid, 0, 0);
do_checked(ptrace, PTRACE_INTERRUPT, pid, 0, 0);
do_checked(waitpid, pid, 0, 0);
do_checked(ptrace, PTRACE_SETOPTIONS, pid, 0, PTRACE_O_EXITKILL);
// Run
int lastsig = 0;
char syscall_result = 0;
for (;;) {
int status;
// Wait for next event
run_checked(ptrace, PTRACE_SYSCALL, pid, 0, lastsig);
run_checked(waitpid, pid, &status, 0);
do_checked(ptrace, PTRACE_SYSCALL, pid, 0, lastsig);
do_checked(waitpid, pid, &status, 0);
// Handle exit
if (WIFEXITED(status)) {
exit(WEXITSTATUS(status));
@ -86,7 +96,7 @@ void run(int pid, int *sync, int *control_pipe) {
lastsig = 0;
// Must be a syscall; handle it
struct user_regs_struct regs;
run_checked(ptrace, PTRACE_GETREGS, pid, 0, &regs);
do_checked(ptrace, PTRACE_GETREGS, pid, 0, &regs);
switch (regs.orig_rax) {
case SYS_execve: {
if (!syscall_result) {
@ -94,14 +104,7 @@ void run(int pid, int *sync, int *control_pipe) {
char filename[PATH_MAX];
size_t filename_len = read_string(filename, sizeof(filename), pid, regs.rdi);
// Check if allowed to execute
char potential_errno;
if (is_rel_path(filename)) {
char *absolute_path = get_pid_rel_path(pid, filename, filename_len);
potential_errno = check_executable(absolute_path, strlen(absolute_path), control_pipe);
free(absolute_path);
} else {
potential_errno = check_executable(filename, filename_len, control_pipe);
}
execontrol_errno_t potential_errno = can_file_maybe_relative(EXECONTROL_CAN_EXEC, pid, filename, filename_len, control_pipe).error;
// Don't pass through if not allowed to
if (potential_errno <= 0) {
regs.orig_rax = -1;
@ -112,8 +115,8 @@ void run(int pid, int *sync, int *control_pipe) {
case SYS_clone: {
if (syscall_result) {
// Attach to it
int npid = regs.rax;
run_checked(write, sync[1], &npid, sizeof(npid));
execontrol_pid_t npid = regs.rax;
do_checked(write, sync[1], &npid, sizeof(npid));
}
} break;
case SYS_execveat: {
@ -123,24 +126,20 @@ void run(int pid, int *sync, int *control_pipe) {
}
// Toggle syscall_result flag
syscall_result = !syscall_result;
run_checked(ptrace, PTRACE_SETREGS, pid, 0, &regs);
do_checked(ptrace, PTRACE_SETREGS, pid, 0, &regs);
}
}
}
}
void end() {
kill(getppid(), SIGINT);
}
int main(int argc, char **argv) {
// Check arguments
if (argc < 2) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <pipe> <executable> [args...]\n", argv[0]);
return -EINVAL;
}
// Get arguments
char *control_pipe_path= argv[1],
char *control_pipe_path = argv[1],
*executable = argv[2],
**executable_argv = argv+2;
size_t control_pipe_path_len = strlen(control_pipe_path);
@ -155,39 +154,52 @@ int main(int argc, char **argv) {
memcpy(control_pipe_full_path[1]+control_pipe_path_len, ".out", sizeof(".out"));
mkfifo(control_pipe_full_path[0], 0600);
mkfifo(control_pipe_full_path[1], 0600);
int control_pipe_write_end_ = do_checked(open, control_pipe_full_path[1], O_WRONLY); // We need to do open the files in opposite order to not get stuck
int control_pipe[] = {
run_checked(open, control_pipe_full_path[0], O_RDONLY),
run_checked(open, control_pipe_full_path[1], O_WRONLY)
do_checked(open, control_pipe_full_path[0], O_RDONLY),
control_pipe_write_end_
};
# ifdef NDEBUG
unlink(control_pipe_full_path[0]);
unlink(control_pipe_full_path[1]);
# endif
free(control_pipe_full_path[0]);
free(control_pipe_full_path[1]);
// Create synchronization buffer
int sync[2];
run_checked(pipe, sync);
do_checked(pipe, sync);
char syncbuf[1];
// Start process
pid_t pid = fork();
execontrol_pid_t pid = fork();
if (pid == 0) {
run_checked(write, sync[1], syncbuf, sizeof(syncbuf));
// Run given program if not init, else run init
do_checked(write, sync[1], syncbuf, sizeof(syncbuf));
// Close pipes
close(sync[0]);
close(sync[1]);
close(control_pipe[0]);
close(control_pipe[1]);
// Run given program
execvp(executable, executable_argv);
perror(executable);
return -errno;
}
run_checked(read, sync[0], syncbuf, sizeof(syncbuf));
do_checked(read, sync[0], syncbuf, sizeof(syncbuf));
// Run
if (fork() == 0) {
atexit(end);
run(pid, sync, control_pipe);
exit(0);
}
// Attach to all further processes
for(;;) {
int npid;
run_checked(read, sync[0], &npid, sizeof(npid));
if (fork() == 0) {
run(npid, sync, control_pipe);
return 0;
if (fork() == 0) {
for(;;) {
execontrol_pid_t npid;
do_checked(read, sync[0], &npid, sizeof(npid));
if (fork() == 0) {
run(npid, sync, control_pipe);
return 0;
}
}
}
// Wait for process to exit
waitpid(pid, NULL, 0);
}

143
test_whitelist.c Normal file
View file

@ -0,0 +1,143 @@
#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
// Custom getline() implementation because the std one somehow didn't work
ssize_t my_getline(char **out, size_t *out_size, FILE *in) {
size_t out_idx = 0;
// Allocate buffer if needed
if (*out == NULL) {
*out = malloc(1);
*out_size = 1;
}
// Loop until \n
for (;;) {
// Read single character
char c;
if (fread(&c, 1, 1, in) < 0 || feof(in)) {
return -1;
}
// Add it to buffer
if (*out_size < out_idx + 1) {
(*out_size)++;
*out = realloc(*out, *out_size);
}
(*out)[out_idx++] = c;
// Break on \n
if (c == '\n') {
break;
}
}
// Return success
return out_idx + 1;
}
char **read_whitelist(const char *whitelist_path) {
// Open file
FILE *whitelist_file = fopen(whitelist_path, "r");
// Read line for line
size_t whitelist_size = sizeof(char**)*2,
whitelist_top = 0;
char **whitelist = malloc(whitelist_size);
while (!feof(whitelist_file)) {
char *executable = NULL;
size_t executable_len;
if (my_getline(&executable, &executable_len, whitelist_file) >= 0 && executable_len != 0) {
executable[executable_len-1] = '\0';
printf("Added to whitelist: <%zu> %s\n", executable_len, executable);
if (whitelist_top*sizeof(char**) >= whitelist_size-1) {
whitelist_size = (whitelist_top+6)*sizeof(char**);
whitelist = realloc(whitelist, whitelist_size);
}
whitelist[whitelist_top++] = executable;
}
}
// Clean up
whitelist[whitelist_top] = NULL;
fclose(whitelist_file);
return whitelist;
}
void free_whitelist(char **whitelist) {
for (char **item = whitelist; *item != NULL; item++) {
free(*item);
}
free(whitelist);
}
int main(int argc, char **argv) {
// Check arguments
if (argc < 3) {
fprintf(stderr, "Usage: %s <pipe> <whitelist>\n", argv[0]);
return -EINVAL;
}
// Get arguments
char *control_pipe_path = argv[1],
*whitelist_path = argv[2];
size_t control_pipe_path_len = strlen(control_pipe_path);
// Open control pipe
char *control_pipe_full_path[] = {
malloc(control_pipe_path_len+sizeof(".out")),
malloc(control_pipe_path_len+sizeof(".in"))
};
memcpy(control_pipe_full_path[0], control_pipe_path, control_pipe_path_len);
memcpy(control_pipe_full_path[1], control_pipe_path, control_pipe_path_len);
memcpy(control_pipe_full_path[0]+control_pipe_path_len, ".out", sizeof(".out"));
memcpy(control_pipe_full_path[1]+control_pipe_path_len, ".in", sizeof(".in"));
mkfifo(control_pipe_full_path[0], 0600);
mkfifo(control_pipe_full_path[1], 0600);
FILE *control_pipe[] = {
fopen(control_pipe_full_path[0], "rb"),
fopen(control_pipe_full_path[1], "wb")
};
free(control_pipe_full_path[0]);
free(control_pipe_full_path[1]);
// Read whitelist
char **whitelist = read_whitelist(whitelist_path);
// Read each command
for (;;) {
// Read command
struct execontrol_request request;
do_checked(fread, &request, sizeof(request), 1, control_pipe[0]);
// Process command
switch (request.cmd) {
case EXECONTROL_CAN_EXEC: {
// Read executable name
char *executable = NULL;
size_t executable_len;
do_checked(getdelim, &executable, &executable_len, '\0', control_pipe[0]);
printf("Got CAN_EXEC request for file: %s\nChecking whitelist... ", executable);
// Check if file is in whitelist
int whitelisted = 0;
for (char **item = whitelist; *item != NULL; item++) {
if (strcmp(*item, executable) == 0) {
whitelisted = 1;
printf("Hit!\n");
break;
}
}
if (!whitelisted) {
printf("Miss.\n");
}
// Act accordingly
struct execontrol_response response = {
.error = whitelisted?EXECONTROL_OKAY:EXECONTROL_UNSPECIFIED_ERROR
};
do_checked(fwrite, &response, sizeof(response), 1, control_pipe[1]);
// Clean up
free(executable);
} break;
}
// Flush response down the pipe
do_checked(fflush, control_pipe[1]);
}
// Clean up
free_whitelist(whitelist);
}