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

Feature/virtual mem flags parse #3606

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
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`
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/IBM/fluent-forward-go v0.2.1
github.com/Masterminds/sprig/v3 v3.2.3
github.com/aquasecurity/libbpfgo v0.5.0-libbpf-1.2
github.com/aquasecurity/libbpfgo/helpers v0.4.6-0.20230321190037-f591a2c5734f
github.com/aquasecurity/libbpfgo/helpers v0.4.6-0.20231023122045-7a58b8ec6cc3
github.com/aquasecurity/tracee/types v0.0.0-20230929172705-29c9b630aeed
github.com/containerd/containerd v1.7.0
github.com/docker/docker v23.0.5+incompatible
Expand All @@ -27,7 +27,7 @@ require (
github.com/urfave/cli/v2 v2.3.0
go.uber.org/goleak v1.1.12
go.uber.org/zap v1.24.0
golang.org/x/sys v0.8.0
golang.org/x/sys v0.13.0
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.56.2
google.golang.org/protobuf v1.30.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ github.com/aquasecurity/libbpfgo v0.5.0-libbpf-1.2 h1:Yywi9wC3GPDOgR8wr6P9geY2qv
github.com/aquasecurity/libbpfgo v0.5.0-libbpf-1.2/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk=
github.com/aquasecurity/libbpfgo/helpers v0.4.6-0.20230321190037-f591a2c5734f h1:l127H3NqJBmw+XMt+haBOeZIrBppuw7TJz26cWMI9kY=
github.com/aquasecurity/libbpfgo/helpers v0.4.6-0.20230321190037-f591a2c5734f/go.mod h1:j/TQLmsZpOIdF3CnJODzYngG4yu1YoDCoRMELxkQSSA=
github.com/aquasecurity/libbpfgo/helpers v0.4.6-0.20231023122045-7a58b8ec6cc3 h1:Znl2aCgGDhJmsJTd5FG753cZpzyBZD6OU4CBE+yqTxs=
github.com/aquasecurity/libbpfgo/helpers v0.4.6-0.20231023122045-7a58b8ec6cc3/go.mod h1:RKGRrng1b+79fklwa81IP98tt4S3ioCM/x6TFcnxbDU=
github.com/aquasecurity/tracee/types v0.0.0-20230929172705-29c9b630aeed h1:d8ehyRJFP5qS0zfTss65UC2SoF7WX+/4+UcTL/73x9M=
github.com/aquasecurity/tracee/types v0.0.0-20230929172705-29c9b630aeed/go.mod h1:sorAC3uGSNB/b8A9PjG1MSbENl81dR7azTSj+HclE+A=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
Expand Down Expand Up @@ -650,6 +652,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
Expand Down
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 @@ -4861,6 +4861,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: true},
},
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
Loading