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

fix(driver): null pointer deref in kernels >=6.5 #1409

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions driver/bpf/fillers.h
Original file line number Diff line number Diff line change
Expand Up @@ -2234,12 +2234,16 @@ static __always_inline bool get_exe_upper_layer(struct dentry *dentry, struct su
return true;
}

struct ovl_entry *oe = (struct ovl_entry*)_READ(dentry->d_fsdata);
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)
struct ovl_entry *oe = (struct ovl_entry*)_READ(dentry->d_fsdata);
unsigned long has_upper = (unsigned long)_READ(oe->has_upper);
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
struct ovl_entry *oe = (struct ovl_entry*)_READ(dentry->d_fsdata);
unsigned long flags = _READ(oe->flags);
unsigned long has_upper = (flags & (1U << (OVL_E_UPPER_ALIAS)));
#else
unsigned long flags = (unsigned long)_READ(dentry->d_fsdata);
unsigned long has_upper = (flags & (1U << (OVL_E_UPPER_ALIAS)));
#endif

if(has_upper)
Expand Down
5 changes: 5 additions & 0 deletions driver/modern_bpf/definitions/missing_definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -1557,4 +1557,9 @@
#define MODULE_INIT_COMPRESSED_FILE 4
/*==================================== FINIT FLAGS ================================*/

/*==================================== OVERLAY FLAGS ================================*/
#define DCACHE_DISCONNECTED 0x20
#define OVL_E_UPPER_ALIAS 0
/*==================================== OVERLAY FLAGS ================================*/

#endif /* __MISSING_DEFINITIONS_H__ */
5 changes: 5 additions & 0 deletions driver/modern_bpf/definitions/struct_flavors.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ struct inode___v6_6 {
struct timespec64 __i_ctime;
};

struct ovl_entry___before_v6_5
{
long unsigned int flags;
};

#ifndef BPF_NO_PRESERVE_ACCESS_INDEX
#pragma clang attribute pop
#endif
Expand Down
33 changes: 31 additions & 2 deletions driver/modern_bpf/helpers/extract/extract_from_kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ static __always_inline void extract__egid(struct task_struct *task, u32 *egid)
// EXECVE FLAGS EXTRACTION
////////////////////////

static __always_inline bool extract__exe_upper_layer(struct inode *inode)
static __always_inline bool extract__exe_upper_layer(struct inode *inode, struct file *exe_file)
{
unsigned long sb_magic = BPF_CORE_READ(inode, i_sb, s_magic);

Expand All @@ -815,10 +815,39 @@ static __always_inline bool extract__exe_upper_layer(struct inode *inode)

bpf_probe_read_kernel(&upper_dentry, sizeof(upper_dentry), vfs_inode + inode_size);

if(upper_dentry)
if(!upper_dentry)
{
return false;
}

struct dentry *dentry = (struct dentry *)BPF_CORE_READ(exe_file, f_path.dentry);

unsigned int d_flags = BPF_CORE_READ(dentry, d_flags);
bool disconnected = (d_flags & DCACHE_DISCONNECTED);
if(disconnected)
{
return true;
}

unsigned long flags = 0;
if(bpf_core_field_exists(((struct ovl_entry___before_v6_5*)0)->flags))
{
// kernel <6.5
struct ovl_entry___before_v6_5 *oe = (struct ovl_entry___before_v6_5*)BPF_CORE_READ(dentry, d_fsdata);
flags = (unsigned long)BPF_CORE_READ(oe, flags);
}
else
{
// In kernels >=6.5 d_fsdata represents an ovl_entry_flag.
flags = (unsigned long)BPF_CORE_READ(dentry, d_fsdata);
}

unsigned long has_upper = (flags & (1U << (OVL_E_UPPER_ALIAS)));
therealbobo marked this conversation as resolved.
Show resolved Hide resolved
if(has_upper)
{
return true;
}

}

return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ int BPF_PROG(t1_sched_p_exec,
{
flags |= PPM_EXE_WRITABLE;
}
if(extract__exe_upper_layer(exe_inode))
if(extract__exe_upper_layer(exe_inode, exe_file))
{
flags |= PPM_EXE_UPPER_LAYER;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ int BPF_PROG(t1_execve_x,
{
flags |= PPM_EXE_WRITABLE;
}
if(extract__exe_upper_layer(exe_inode))
if(extract__exe_upper_layer(exe_inode, exe_file))
{
flags |= PPM_EXE_UPPER_LAYER;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ int BPF_PROG(t1_execveat_x,
{
flags |= PPM_EXE_WRITABLE;
}
if(extract__exe_upper_layer(exe_inode))
if(extract__exe_upper_layer(exe_inode, exe_file))
{
flags |= PPM_EXE_UPPER_LAYER;
}
Expand Down
14 changes: 8 additions & 6 deletions driver/ppm_fillers.c
Original file line number Diff line number Diff line change
Expand Up @@ -934,32 +934,34 @@ bool ppm_is_upper_layer(struct file *exe_file){
if(sb)
{
sb_magic = sb->s_magic;
if(sb_magic == PPM_OVERLAYFS_SUPER_MAGIC)
struct ovl_entry *oe = (struct ovl_entry*)(exe_file->f_path.dentry->d_fsdata);
if(sb_magic == PPM_OVERLAYFS_SUPER_MAGIC && oe)
{
struct ovl_entry *oe = (struct ovl_entry*)(exe_file->f_path.dentry->d_fsdata);
unsigned long has_upper = 0;
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 13, 0)
if(oe->__upperdentry)
{
return true;
}
#else
struct dentry **upper_dentry = NULL;
struct dentry *upper_dentry = NULL;
unsigned int d_flags = exe_file->f_path.dentry->d_flags;
bool disconnected = (d_flags & DCACHE_DISCONNECTED);

// Pointer arithmetics due to unexported ovl_inode struct
// warning: this works if and only if the dentry pointer
// is placed right after the inode struct
upper_dentry = (struct dentry **)((char *)exe_file->f_path.dentry->d_inode + sizeof(struct inode));
upper_dentry = (struct dentry *)((char *)exe_file->f_path.dentry->d_inode + sizeof(struct inode));

#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 16, 0)
has_upper = oe->has_upper;
#else
#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 5, 0)
has_upper = test_bit(OVL_E_UPPER_ALIAS, &(oe->flags));
#else
has_upper = test_bit(OVL_E_UPPER_ALIAS, (unsigned long*)&oe);
#endif

if(*upper_dentry && (has_upper || disconnected))
if(upper_dentry && (has_upper || disconnected))
{
return true;
}
Expand Down
197 changes: 197 additions & 0 deletions test/drivers/test_suites/syscall_exit_suite/execve_x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,203 @@ TEST(SyscallExit, execveX_success)
evt_test->assert_num_params_pushed(28);
}

TEST(SyscallExit, execveX_not_upperlayer)
{
auto evt_test = get_syscall_event_test(__NR_execve, EXIT_EVENT);

evt_test->enable_capture();

/*=============================== TRIGGER SYSCALL ===========================*/

const char lowerdir[] = "/bin";
const char upperdir[] = "/tmp/upper";
const char target[] = "/tmp/merged";
char tmp_template[] = "/tmp/tmpdir.XXXXXX";
char *mntopts;

/* Create a temporary directory for the work layer */
char *workdir = mkdtemp(tmp_template);

/* Create the overlay mount target directory */
mkdir(upperdir, 0777);
mkdir(target, 0777);

/* Construct the mount options string */
if(asprintf(&mntopts, "lowerdir=%s,upperdir=%s,workdir=%s", lowerdir, upperdir, workdir) == -1){
FAIL() << "Cannot construct mount options string";
};

/* Mount the overlayfs */
if (mount("overlay", target, "overlay", MS_MGC_VAL, mntopts) != 0)
{
FAIL() << "Cannot mount overlay." << std::endl;
}

/* Copy /bin/true to /tmp/merged/uppertrue in the overlay file system */
char true_path[1024], upper_exe_path[1024];
sprintf(true_path, "%s/true", lowerdir);
sprintf(upper_exe_path, "%s/uppertrue", target);

int true_fd = open(true_path, O_RDONLY);
if (true_fd == -1)
{
FAIL() << "Cannot open /bin/true." << std::endl;
}

int upper_exe_fd = open(upper_exe_path, O_WRONLY|O_CREAT, 0777);
if (upper_exe_fd == -1)
{
FAIL() << "Cannot open /tmp/merged/uppertrue." << std::endl;
}

char buf[1024];
ssize_t bytes_read;
while ((bytes_read = read(true_fd, buf, sizeof(buf))) > 0)
{
if (write(upper_exe_fd, buf, bytes_read) != bytes_read)
{
FAIL() << "Cannot write /tmp/merged/uppertrue." << std::endl;
}
}

if (bytes_read == -1)
{
FAIL() << "Error copying /bin/true" << std::endl;
}

if (close(true_fd) == -1)
{
FAIL() << "Error closing /bin/true" << std::endl;
}

if (close(upper_exe_fd) == -1)
{
FAIL() << "Error closing /tmp/merged/uppertrue" << std::endl;
}

/* Prepare the execve args */
const char *pathname = true_path;
const char *comm = "true";
const char *argv[] = {true_path, "randomarg", NULL};
const char *envp[] = {"IN_TEST=yes", "3_ARGUMENT=yes", "2_ARGUMENT=no", NULL};

/* We need to use `SIGCHLD` otherwise the parent won't receive any signal
* when the child terminates.
*/
struct clone_args cl_args = {0};
cl_args.exit_signal = SIGCHLD;
pid_t ret_pid = syscall(__NR_clone3, &cl_args, sizeof(cl_args));

/*
* Call the `execve`
*/
if(ret_pid == 0)
{
syscall(__NR_execve, pathname, argv, envp);
exit(EXIT_FAILURE);
}

assert_syscall_state(SYSCALL_SUCCESS, "clone3", ret_pid, NOT_EQUAL, -1);

/* Catch the child before doing anything else. */
int status = 0;
int options = 0;
assert_syscall_state(SYSCALL_SUCCESS, "wait4", syscall(__NR_wait4, ret_pid, &status, options, NULL), NOT_EQUAL, -1);

if(__WEXITSTATUS(status) == EXIT_FAILURE || __WIFSIGNALED(status) != 0)
{
FAIL() << "The child execve failed." << std::endl;
}

/*=============================== TRIGGER SYSCALL ===========================*/

evt_test->disable_capture();

/* Unmount the overlay file system */
if (umount(target))
{
FAIL() << "Cannot unmount target dir." << std::endl;
}

/* Remove the upper and work directories */
rmdir(upperdir);
rmdir(workdir);
rmdir(target);

/* We search for a child event. */
evt_test->assert_event_presence(ret_pid);

if(HasFatalFailure())
{
return;
}

evt_test->parse_event();

evt_test->assert_header();

/*=============================== ASSERT PARAMETERS ===========================*/

/* Please note here we cannot assert all the params, we check only the possible ones. */

/* Parameter 1: res (type: PT_ERRNO)*/
evt_test->assert_numeric_param(1, (int64_t)0);

/* Parameter 2: exe (type: PT_CHARBUF) */
evt_test->assert_charbuf_param(2, pathname);

/* Parameter 3: args (type: PT_CHARBUFARRAY) */
/* Starting from `1` because the first is `exe`. */
evt_test->assert_charbuf_array_param(3, &argv[1]);

/* Parameter 4: tid (type: PT_PID) */
evt_test->assert_numeric_param(4, (int64_t)ret_pid);

/* Parameter 5: pid (type: PT_PID) */
/* We are the main thread of the process so it's equal to `tid`. */
evt_test->assert_numeric_param(5, (int64_t)ret_pid);

/* Parameter 6: ptid (type: PT_PID) */
evt_test->assert_numeric_param(6, (int64_t)::getpid());

/* Parameter 7: cwd (type: PT_CHARBUF) */
/* leave the current working directory empty like in the old probe. */
evt_test->assert_empty_param(7);

/* Parameter 14: comm (type: PT_CHARBUF) */
evt_test->assert_charbuf_param(14, comm);

/* Parameter 15: cgroups (type: PT_CHARBUFARRAY) */
evt_test->assert_cgroup_param(15);

/* Parameter 16: env (type: PT_CHARBUFARRAY) */
evt_test->assert_charbuf_array_param(16, &envp[0]);

/* PPM_EXE_WRITABLE is set when the user that executed a process can also write to the executable
* file that is used to spawn it or is its owner or otherwise capable.
*/
evt_test->assert_numeric_param(20, (uint32_t)PPM_EXE_WRITABLE, EQUAL);

/* Parameter 24: exe_file ino (type: PT_UINT64) */
evt_test->assert_numeric_param(24, (uint64_t)1, GREATER_EQUAL);

/* Parameter 25: exe_file ctime (last status change time, epoch value in nanoseconds) (type: PT_ABSTIME) */
evt_test->assert_numeric_param(25, (uint64_t)1000000000000000000, GREATER_EQUAL);

/* Parameter 26: exe_file mtime (last modifitrueion time, epoch value in nanoseconds) (type: PT_ABSTIME) */
evt_test->assert_numeric_param(26, (uint64_t)1000000000000000000, GREATER_EQUAL);

/* Parameter 27: euid (type: PT_UID) */
evt_test->assert_numeric_param(27, (uint32_t)geteuid(), EQUAL);

/* Parameter 28: trusted_exepath (type: PT_FSPATH) */
evt_test->assert_charbuf_param(28, "/usr/bin/true");

/*=============================== ASSERT PARAMETERS ===========================*/

evt_test->assert_num_params_pushed(28);
}

TEST(SyscallExit, execveX_upperlayer_success)
{
auto evt_test = get_syscall_event_test(__NR_execve, EXIT_EVENT);
Expand Down