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

chore(ebpf): use core type size checks #4003

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

NDStrahilevitz
Copy link
Collaborator

@NDStrahilevitz NDStrahilevitz commented Apr 24, 2024

1. Explain what the PR does

1156108 chore(ebpf): replace sizeof with bpf type helpers

When using sizeof on kernel structs, false results may happen due to the
sizeof builtin not being designed to take into account btf relocations.

Use the libbpf helpers bpf_core_type_size and bpf_core_field_size where
relevant.

cfd849e chore(ebpf): remove get_type_size macro

Use the builtin libbpf core helper instead, which
the macro was an alias of.

2. Explain how to test it

3. Other comments

Resolve #3774

@NDStrahilevitz
Copy link
Collaborator Author

This breaks verifier right now :/

@NDStrahilevitz NDStrahilevitz force-pushed the core_type_size_checks branch 3 times, most recently from 96212d3 to 1156108 Compare April 24, 2024 16:11
@NDStrahilevitz NDStrahilevitz marked this pull request as ready for review April 24, 2024 16:11
Copy link
Collaborator

@yanivagman yanivagman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some places we read a struct into a stack variable which was preallocated by the compiler according to the known size at compile time. I believe this may cause future issues and we should avoid that (so just leave as is for now)

@@ -171,7 +171,7 @@ statfunc size_t get_path_str_buf(struct path *path, buf_t *out_buf)
}

struct path f_path;
bpf_probe_read_kernel(&f_path, sizeof(struct path), path);
bpf_probe_read_kernel(&f_path, bpf_core_type_size(struct path), path);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's a good idea to do that here.
The compiler preallocates space on the stack for f_path using the size of struct path known at compile time.
If the size of struct path is different on the user environment, it can override other other variables on the stack

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, seems reasonable to move back to sizeof in order to correspond with the the compiler preallocation.
However, doesn't this imply that the compiler might preallocate the wrong size?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it seems that in this function we can and should just rely on BPF_CORE_READ, and remove the probe_read on the struct itself, WDYT?

@@ -427,7 +427,7 @@ statfunc void fill_vfs_file_bin_args_io_data(io_data_t io_data, bin_args_t *bin_
bin_args->iov_len = io_data.len;
bin_args->iov_idx = 0;
struct iovec io_vec;
bpf_probe_read_kernel(&io_vec, sizeof(struct iovec), &bin_args->vec[0]);
bpf_probe_read_kernel(&io_vec, bpf_core_type_size(struct iovec), &bin_args->vec[0]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -468,7 +468,11 @@ statfunc void fill_file_header(u8 header[FILE_MAGIC_HDR_SIZE], io_data_t io_data
bpf_probe_read(header, len, io_data.ptr);
} else {
struct iovec io_vec;
// NOTE(nadav.str): can't use bpf_core_type_size in memset.
// Maybe it only resolves in runtime, although its a macro so it seems unlikely.
// Anyway, leaving it with sizeof for now.
__builtin_memset(&io_vec, 0, sizeof(io_vec));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -369,7 +369,7 @@ statfunc struct sockaddr_un get_unix_sock_addr(struct unix_sock *sock)
struct unix_address *addr = BPF_CORE_READ(sock, addr);
int len = BPF_CORE_READ(addr, len);
struct sockaddr_un sockaddr = {};
if (len <= sizeof(struct sockaddr_un)) {
if (len <= bpf_core_type_size(struct sockaddr_un)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -2641,7 +2644,7 @@ int BPF_KPROBE(trace_security_socket_connect)
// Workaround for sockaddr_un struct length (issue: #1129).
struct sockaddr_un sockaddr = {0};
bpf_probe_read(&sockaddr, (u32) addr_len, (void *) address);
stsb(args_buf, (void *) &sockaddr, sizeof(struct sockaddr_un), 2);
stsb(args_buf, (void *) &sockaddr, bpf_core_type_size(struct sockaddr_un), 2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

// NOTE(nadav.str): sizeof used instead of bpf_core_type_size in this case.
// bpf_core_type_size is evaluated at runtime. thus the verifier isn't satisfied
// with the boundary established below for addr_len. therefore, bpf_probe_read
// causes the verifier to fail.
if (addr_len <= sizeof(struct sockaddr_un)) {
struct sockaddr_un sockaddr = {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -2871,7 +2881,8 @@ statfunc u32 send_bin_helper(void *ctx, void *prog_array, int tail_call)
// Handle the rest of write recursively
bin_args->start_off += bin_args->full_size;
struct iovec io_vec;
bpf_probe_read(&io_vec, sizeof(struct iovec), &bin_args->vec[bin_args->iov_idx]);
bpf_probe_read(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -2958,7 +2969,8 @@ statfunc u32 send_bin_helper(void *ctx, void *prog_array, int tail_call)
// Handle the rest of write recursively
bin_args->start_off += bin_args->full_size;
struct iovec io_vec;
bpf_probe_read(&io_vec, sizeof(struct iovec), &bin_args->vec[bin_args->iov_idx]);
bpf_probe_read(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@NDStrahilevitz
Copy link
Collaborator Author

I'm not sure it's a good idea to do that here.
The compiler preallocates space on the stack for f_path using the size of struct path known at compile time.
If the size of struct path is different on the user environment, it can override other other variables on the stack

@yanivagman it seems to me we should rather avoid stack allocations for kernel structs. That is, it seems that if sizeof of the stack allocated variable may turn out wrong across kernels, then the stack allocation size itself might be wrong. Not even sure we can rely on the stack allocation to be the maximum bound (then it wouldn't be such a problem), because we rely on a minimal vmlinux.h file.
Question is, is my assessment correct, and if so, should we make the effort in the scope of this PR?

@NDStrahilevitz NDStrahilevitz force-pushed the core_type_size_checks branch 2 times, most recently from d04ba14 to 0dbe6cb Compare May 7, 2024 15:37
Use the builtin libbpf core helper instead, which
the macro was an alias of.
When using sizeof on kernel structs, false results may happen due to the
sizeof builtin not being designed to take into account btf relocations.

Use the libbpf helpers bpf_core_type_size and bpf_core_field_size where
relevant.

NOTE: iovec size checks were left unchanged, since they are currently
using copies to stack allocated variables.
The helper previously copied the path argument into a stack allocated
copy. This is an unnecessary copy, and risks copying the wrong size,
since the actual kernel specific size may differ from the compiler
allocated size.
Instead, use CORE_READ to access the necessary fields directly from the
argument.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracee should always rely on get_type_size() instead of sizeof() for correct CORE type sizes
3 participants