Skip to content

Commit

Permalink
feat(events): create access_remote_vm event
Browse files Browse the repository at this point in the history
An event for accessing the memroy of a process externally (can be the
same process) by the mem file of the process in procfs.

Co-authored-by: OriGlassman <39296766+origlassman@users.noreply.github.com>
  • Loading branch information
AlonZivony and OriGlassman committed Oct 25, 2023
1 parent 02dad67 commit 0794743
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 1 deletion.
34 changes: 34 additions & 0 deletions docs/docs/events/builtin/extra/access_remote_vm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# access_remote_vm

## Intro
access_remote_vm - gain access to the virtual memory of a separate process through the use of the procfs mem file.

## Description
This event marks access attempt of a process to the virtual memory of another process using the procfs mem file associated with that specific process (/proc/<pid>/mem).
It is a more elaborated event than the `security_file_open` of the mem file.

## Arguments
* `remote_pid`: `int`[K] - PID of the process the memory area belongs to.
* `start_address`: `void *`[K] - Start address of the operation.
* `gup_flags`: `unsigned int`[K] - Flags for get_user_pages operation.
* `vm_flags`: `unsigned long`[K] - Virtual memory flags.
* `mapped.path`: `const char*`[K] - Path of the mapped file, or the name of memory area if no file is mapped.
* `mapped.device_id`: `dev_t`[K,OPT] - Device ID of the mapped file.
* `mapped.inode_number`: `unsigned long`[K,OPT] - Inode number of the mapped file.
* `mapped.ctime`: `unsigned long`[K,OPT] - Creation time of the mapped file.

## Hooks
### get_user_pages_remote
#### Type
kprobe + kretprobe
#### Purpose
The main function that implements the access to the virtual memory area of the other process.

### generic_access_phys
#### Type
kprobe
#### Purpose
A fallback function, implementing the `access` method of the `vma_operations` struct for most of the vmas. It is used to access special memory areas.

## Related Events
`security_file_open`,`security_mmap_file`,`vfs_write`,`vfs_writev`,`vfs_read`,`vfs_readv`
63 changes: 63 additions & 0 deletions pkg/ebpf/c/common/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <vmlinux.h>

#include <common/common.h>
#include <common/ksymbols.h>

// PROTOTYPES

Expand All @@ -12,7 +13,9 @@ statfunc unsigned long get_arg_start_from_mm(struct mm_struct *);
statfunc unsigned long get_arg_end_from_mm(struct mm_struct *);
statfunc unsigned long get_env_start_from_mm(struct mm_struct *);
statfunc unsigned long get_env_end_from_mm(struct mm_struct *);
statfunc struct task_struct *get_owner_task_from_mm(struct mm_struct *);
statfunc unsigned long get_vma_flags(struct vm_area_struct *);
statfunc int get_vma_location(struct vm_area_struct *, size_t, char *);

// FUNCTIONS

Expand Down Expand Up @@ -41,11 +44,71 @@ statfunc unsigned long get_env_end_from_mm(struct mm_struct *mm)
return BPF_CORE_READ(mm, env_end);
}

statfunc struct task_struct *get_owner_task_from_mm(struct mm_struct *mm)
{
return BPF_CORE_READ(mm, owner);
}

statfunc struct file *get_mapped_file_from_vma(struct vm_area_struct *vma)
{
return BPF_CORE_READ(vma, vm_file);
}

statfunc unsigned long get_vma_flags(struct vm_area_struct *vma)
{
return BPF_CORE_READ(vma, vm_flags);
}

// The function uses the "special_mapping_vmops" and "legacy_special_mapping_vmops" symbols, so
// they should be requested by the event using this function.
statfunc bool is_special_mapping(struct vm_area_struct *vma)
{
char special_mapping_vmops_symbol[22] = "special_mapping_vmops";
void *special_mapping_vmops_addr = get_symbol_addr(special_mapping_vmops_symbol);
char legacy_special_mapping_vmops_symbol[29] = "legacy_special_mapping_vmops";
void *legacy_special_mapping_vmops_addr = get_symbol_addr(legacy_special_mapping_vmops_symbol);
const struct vm_operations_struct *vm_ops = BPF_CORE_READ(vma, vm_ops);
if (vm_ops != NULL &&
(vm_ops == special_mapping_vmops_addr || vm_ops == legacy_special_mapping_vmops_addr)) {
return true;
}
return false;
}

// Return the name of the location related to the given vma. For example [stack], [heap] and [vdso].
// This function doesn't resolve file mapped vmas, so if the vma is a memory mapped file its
// details should be collected in a different way.
// The function uses the "special_mapping_vmops" and "legacy_special_mapping_vmops" symbols, so
// they should be requested by the event using this function.
statfunc int get_vma_location(struct vm_area_struct *vma, size_t max_len, char *location)
{
// Avoid buffer overflow and satisfy the verifier.
if (max_len < 7) {
return 0;
}
struct mm_struct *vm_mm = BPF_CORE_READ(vma, vm_mm);
if (vm_mm) {
unsigned long start_stack = BPF_CORE_READ(vm_mm, start_stack);
unsigned long start_brk = BPF_CORE_READ(vm_mm, start_brk);
unsigned long brk = BPF_CORE_READ(vm_mm, brk);
u64 vm_start = BPF_CORE_READ(vma, vm_start);
u64 vm_end = BPF_CORE_READ(vma, vm_end);

if (vm_start <= start_stack && vm_end >= start_stack) {
__builtin_memcpy(location, "[stack]", 7);
} else if (vm_start <= brk && vm_end >= start_brk) {
__builtin_memcpy(location, "[heap]", 6);
} else if (is_special_mapping(vma)) {
struct vm_special_mapping *special_mapping = BPF_CORE_READ(vma, vm_private_data);
const char *name = BPF_CORE_READ(special_mapping, name);
if (name) {
bpf_probe_read_str(location, max_len, name);
}
}
}
return 0;
}

statfunc struct mount *real_mount(struct vfsmount *mnt)
{
return container_of(mnt, struct mount, mnt);
Expand Down
105 changes: 105 additions & 0 deletions pkg/ebpf/c/tracee.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -4866,6 +4866,111 @@ int BPF_KPROBE(trace_ret_exec_binprm2)
return events_perf_submit(&p, PROCESS_EXECUTION_FAILED, ret);
}

// Submit "access_remote_vm" event for the given vma and info.
statfunc int submit_access_remote_vm(program_data_t *p,
struct vm_area_struct *vma,
void *address,
unsigned int gup_flags)
{
unsigned long vm_flags = get_vma_flags(vma);
struct mm_struct *mm = BPF_CORE_READ(vma, vm_mm);
file_info_t file_info = {};

char location[64] = {0};

// Get mapped memory information
struct file *file = get_mapped_file_from_vma(vma);
file_info = get_file_info(file);
// If there is no file that is mapped to the vma, try to get memory location name instead.
if (!file_info.pathname_p) {
get_vma_location(vma, sizeof(location), location);
}

struct task_struct *task = get_owner_task_from_mm(mm);
u32 pid = get_task_host_tgid(task);

save_to_submit_buf(&p->event->args_buf, &pid, sizeof(u32), 0);
save_to_submit_buf(&p->event->args_buf, &address, sizeof(void *), 1);
save_to_submit_buf(&p->event->args_buf, &gup_flags, sizeof(unsigned int), 2);
save_to_submit_buf(&p->event->args_buf, &vm_flags, sizeof(unsigned long), 3);
if (file_info.pathname_p != NULL) {
save_str_to_buf(&p->event->args_buf, file_info.pathname_p, 4);
save_to_submit_buf(&p->event->args_buf, &file_info.id.device, sizeof(dev_t), 5);
save_to_submit_buf(&p->event->args_buf, &file_info.id.inode, sizeof(unsigned long), 6);
save_to_submit_buf(&p->event->args_buf, &file_info.id.ctime, sizeof(u64), 7);
} else {
save_str_to_buf(&p->event->args_buf, location, 4);
}

return events_perf_submit(p, ACCESS_REMOTE_VM, 0);
}

SEC("kprobe/generic_access_phys")
int BPF_KPROBE(trace_generic_access_phys)
{
program_data_t p = {};
if (!init_program_data(&p, ctx))
return 0;

if (!should_trace(&p))
return 0;

struct vm_area_struct *vma = (struct vm_area_struct *) PT_REGS_PARM1(ctx);
unsigned long addr = (unsigned long) PT_REGS_PARM2(ctx);
int write = (int) PT_REGS_PARM5(ctx);
return submit_access_remote_vm(&p, vma, (void *) addr, write);
}

SEC("kprobe/get_user_pages_remote")
TRACE_ENT_FUNC(get_user_pages_remote, ACCESS_REMOTE_VM);

SEC("kretprobe/get_user_pages_remote")
int BPF_KPROBE(trace_ret_get_user_pages_remote)
{
args_t saved_args;
if (load_args(&saved_args, ACCESS_REMOTE_VM) != 0) {
// missed entry or not traced
return 0;
}
del_args(ACCESS_REMOTE_VM);

program_data_t p = {};
if (!init_program_data(&p, ctx))
return 0;

if (!should_trace(&p))
return 0;

int ret_val = PT_REGS_RC(ctx);
// if the page cannot be pinned the return code would be <= 0.
// This could mean that the page is a memory mapped page, for example.
// In this case, the access to that page has to be done through vma->vm_ops->access which we
// trace for most cases (all normal cases) using generic_access_phys.
// So, we let the flow continue so it would be traced there.
if (ret_val <= 0)
return 0;

unsigned long address = (unsigned long) saved_args.args[1];
unsigned int gup_flags = (unsigned int) saved_args.args[3];
struct vm_area_struct **vmas = (struct vm_area_struct **) saved_args.args[5];
struct vm_area_struct *vma = NULL;
unsigned long vm_flags = 0;
file_info_t file_info = {};
// Without a vma we can't get enough information for the event.
// Moreover, this will filter out the calls for it from the execve flow.
if (!vmas)
return 0;

// In the flow of interest, via the access_remote_vm function, the logic iterate over the
// pages 1 by 1. This means that 1 and only 1 vma would be resolved here if the function
// succeed.
bpf_core_read(&vma, sizeof(void *), vmas);
if (!vma)
return 0;

return submit_access_remote_vm(&p, vma, (void *) address, gup_flags);
}

// clang-format off

// Network Packets (works from ~5.2 and beyond)
Expand Down
2 changes: 2 additions & 0 deletions pkg/ebpf/c/tracee.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,7 @@ statfunc int capture_file_write(struct pt_regs *, u32, bool);
statfunc bool filter_file_read_capture(program_data_t *, struct file *, io_data_t, off_t);
statfunc int capture_file_read(struct pt_regs *, u32, bool);
statfunc struct pipe_buffer *get_last_write_pipe_buffer(struct pipe_inode_info *);
statfunc int
submit_access_remote_vm(program_data_t *, struct vm_area_struct *, void *, unsigned int);

#endif
1 change: 1 addition & 0 deletions pkg/ebpf/c/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ enum event_id_e
HIDDEN_KERNEL_MODULE_SEEKER,
MODULE_LOAD,
MODULE_FREE,
ACCESS_REMOTE_VM,
MAX_EVENT_ID,
};

Expand Down
20 changes: 19 additions & 1 deletion pkg/ebpf/c/vmlinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,24 @@ typedef struct {
struct signal_struct {
atomic_t live;
};
typedef unsigned long vm_flags_t;

struct vm_special_mapping {
const char *name;
};

struct vm_area_struct {
long unsigned int vm_flags;
union {
struct {
unsigned long vm_start;
unsigned long vm_end;
};
};
struct mm_struct *vm_mm;
const vm_flags_t vm_flags;
struct file *vm_file;
void *vm_private_data;
const struct vm_operations_struct *vm_ops;
};

typedef unsigned int __kernel_gid32_t;
Expand Down Expand Up @@ -642,9 +656,13 @@ struct mm_struct {
struct {
long unsigned int arg_start;
long unsigned int arg_end;
unsigned long start_brk;
unsigned long brk;
unsigned long start_stack;
long unsigned int env_start;
long unsigned int env_end;
};
struct task_struct *owner;
};

struct vfsmount {
Expand Down
3 changes: 3 additions & 0 deletions pkg/ebpf/probes/probe_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ func NewDefaultProbeGroup(module *bpf.Module, netEnabled bool) (*ProbeGroup, err
SignalSchedProcessFork: NewTraceProbe(RawTracepoint, "sched:sched_process_fork", "sched_process_fork_signal"),
SignalSchedProcessExec: NewTraceProbe(RawTracepoint, "sched:sched_process_exec", "sched_process_exec_signal"),
SignalSchedProcessExit: NewTraceProbe(RawTracepoint, "sched:sched_process_exit", "sched_process_exit_signal"),
GetUserPagesRemote: NewTraceProbe(KProbe, "get_user_pages_remote", "trace_get_user_pages_remote"),
GetUserPagesRemoteRet: NewTraceProbe(KretProbe, "get_user_pages_remote", "trace_ret_get_user_pages_remote"),
GenericAccessPhys: NewTraceProbe(KProbe, "generic_access_phys", "trace_generic_access_phys"),
}

if !netEnabled {
Expand Down
3 changes: 3 additions & 0 deletions pkg/ebpf/probes/probes.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,7 @@ const (
SignalSchedProcessFork
SignalSchedProcessExec
SignalSchedProcessExit
GetUserPagesRemote
GetUserPagesRemoteRet
GenericAccessPhys
)
28 changes: 28 additions & 0 deletions pkg/events/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const (
HiddenKernelModuleSeeker
ModuleLoad
ModuleFree
AccessRemoteVm
MaxCommonID
)

Expand Down Expand Up @@ -11259,6 +11260,33 @@ var CoreEvents = map[ID]Definition{
{Type: "bool", Name: "process_group_exit"},
},
},
AccessRemoteVm: {
id: AccessRemoteVm,
id32Bit: Sys32Undefined,
name: "access_remote_vm",
dependencies: Dependencies{
probes: []Probe{
{handle: probes.GetUserPagesRemote, required: true},
{handle: probes.GetUserPagesRemoteRet, required: true},
{handle: probes.GenericAccessPhys, required: false}, // Exist if CONFIG_HAVE_IOREMAP_PROT
},
kSymbols: []KSymbol{
{symbol: "special_mapping_vmops", required: false},
{symbol: "legacy_special_mapping_vmops", required: false},
},
},
sets: []string{},
params: []trace.ArgMeta{
{Type: "int", Name: "remote_pid"},
{Type: "void *", Name: "start_address"},
{Type: "unsigned int", Name: "gup_flags"},
{Type: "unsigned long", Name: "vm_flags"},
{Type: "const char*", Name: "mapped.path"},
{Type: "dev_t", Name: "mapped.device_id"},
{Type: "unsigned long", Name: "mapped.inode_number"},
{Type: "unsigned long", Name: "mapped.ctime"},
},
},
//
// Begin of Network Protocol Event Types
//
Expand Down

0 comments on commit 0794743

Please sign in to comment.