-
Notifications
You must be signed in to change notification settings - Fork 160
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
docs: add an already existing syscall into the modern probe #781
Conversation
Signed-off-by: Andrea Terzolo <andrea.terzolo@polito.it>
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: Andreagit97 The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Sorry, @dwindsor I'm a bit late, in the meanwhile, you have already learned on your skin how to add a syscall 😂 but i hope that this will help other new contributors maybe :) |
Thanks for this! The diagram of scap_evt is helpful. Should we also update this article with a link to these instructions? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @Andreagit97 very nice and focussed tutorial and it reads fantastic!
* (https://github.com/falcosecurity/libs/blob/master/driver/modern_bpf/helpers/store/ringbuf_store_params.h#L234-L248) | ||
* we need to use the `ringbuf__store_u32` helper. | ||
*/ | ||
ringbuf__store_u32(&ringbuf, access_flags_to_scap(mode)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be worth to have note on xx_flags_to_scap
in sense of abstracting flags for the userspace libs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure I will add it, ty!
TEST(SyscallExit, <your_syscall_name>X) | ||
/**/ | ||
{ | ||
auto evt_test = get_syscall_event_test(<syscall_code>, <event_direction>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering to add the #ifdef __NR_xxxx
to the template to avoid running tests on architectures where a particular syscall does not exist?.
Also, any plans to ship those templates for copy/paste or ultimatively with some m4
magic 😁 ? Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering to add the #ifdef __NR_xxxx to the template to avoid running tests on architectures where a particular syscall does not exist?.
Makes sense
Also, any plans to ship those templates for copy/paste or ultimatively with some m4 magic grin ? Wdyt?
Yeah it could be an idea, I was thinking of a simple cpp
file as a template, but with more time we can think also something more complex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.. let's start simple and smart here!!
* to store a `charbuf` param, so one between `PT_CHARBUF`, `PT_FSPATH`, `PT_FSRELPATH`, we need to use | ||
* the `auxmap__store_charbuf_param` helper. | ||
* - `MAX_PATH` is the maximum length we want to read from the pathname pointer | ||
* - `USER` means that the provided pointer points to userspace memory and not kernel memory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if I should compile something on the kernel vs. user space memory here or elsewhere 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes if you want you can explain a little bit better here why we need this distinction :) it would be amazing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks... then let me write something up and share with you early next week (being on a training the rest of this week).
evt_test->assert_num_params_pushed(2); | ||
} | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
## Handling syscalls with complex parameters | |
Some syscalls' parameters behave differently in the error case. For instance, consider `read(2)`: | |
#include <unistd.h> | |
ssize_t read(int fd, void *buf, size_t count); | |
`read` behaves differently when an error occurs: | |
RETURN VALUE | |
On success, the number of bytes read is returned (zero indicates end of file), and the file position is advanced by this number. It is not | |
an error if this number is smaller than the number of bytes requested; this may happen for example because fewer bytes are actually available | |
right now (maybe because we were close to end-of-file, or because we are reading from a pipe, or from a terminal), or because read() was in‐ | |
terrupted by a signal. See also NOTES. | |
On error, -1 is returned, and errno is set appropriately. In this case, it is left unspecified whether the file position (if any) changes. | |
Writing a driver for this syscall is a bit trickier than usual. In the success case, the | |
exit probe should return the number of bytes read as well as the bytes themselves through the `*buf` parameter. In the error case, the exit probe should return `errno` and nothing/empty for `*buf`. | |
To verify proper driver handling for both error and success cases, two tests should be created: | |
### Complex parameter probe | |
SEC("tp_btf/sys_exit") | |
int BPF_PROG(read_x, | |
struct pt_regs *regs, | |
long ret) | |
{ | |
// ... | |
/*=============================== COLLECT PARAMETERS ===========================*/ | |
/* Parameter 1: res (type: PT_ERRNO) */ | |
auxmap__store_s64_param(auxmap, ret); | |
if(ret > 0) | |
{ | |
/* SUCCESS CASE - push a param containing the read buffer */ | |
/* Parameter 2: data (type: PT_BYTEBUF) */ | |
unsigned long data_pointer = extract__syscall_argument(regs, 1); | |
auxmap__store_bytebuf_param(auxmap, data_pointer, bytes_to_read, USER); | |
} | |
else | |
{ | |
/* ERROR CASE - push an empty param */ | |
/* Parameter 2: data (type: PT_BYTEBUF) */ | |
auxmap__store_empty_param(auxmap); | |
} | |
/*=============================== COLLECT PARAMETERS ===========================*/ | |
// ... | |
} | |
### Complex parameter test (Success case) | |
TEST(SyscallExit, readX) | |
{ | |
// ... | |
/*=============================== TRIGGER SYSCALL ===========================*/ | |
/* Open /dev/urandom for reading */ | |
int fd = syscall(__NR_open, "/dev/urandom", O_RDONLY); | |
assert_syscall_state(SYSCALL_SUCCESS, "open", fd, NOT_EQUAL, -1); | |
/* Read data from /dev/urandom */ | |
const unsigned data_len = 64; | |
char buf[data_len]; | |
ssize_t read_bytes = syscall(__NR_read, fd, (void *)buf, data_len); | |
assert_syscall_state(SYSCALL_SUCCESS, "read", read_bytes, NOT_EQUAL, -1); | |
/* Close /dev/urandom fd */ | |
assert_syscall_state(SYSCALL_SUCCESS, "close", syscall(__NR_close, fd), NOT_EQUAL, -1); | |
/*=============================== TRIGGER SYSCALL ===========================*/ | |
// ... | |
/*=============================== ASSERT PARAMETERS ===========================*/ | |
/* Parameter 1: res (type: PT_ERRNO) */ | |
evt_test->assert_numeric_param(1, (int64_t)read_bytes); | |
/* Parameter 2: data (type: PT_BYTEBUF) */ | |
evt_test->assert_only_param_len(2, (uint32_t)read_bytes); | |
/*=============================== ASSERT PARAMETERS ===========================*/ | |
// ... | |
} | |
### Complex parameter test (Failure case) | |
TEST(SyscallExit, readXfailure) | |
{ | |
// ... | |
/*=============================== TRIGGER SYSCALL ===========================*/ | |
/* Read data from an invalid fd */ | |
const unsigned data_len = 64; | |
char buf[data_len]; | |
ssize_t read_bytes = syscall(__NR_read, -1, (void *)buf, data_len); | |
int errno_value = -errno; | |
assert_syscall_state(SYSCALL_FAILURE, "read", read_bytes); | |
/*=============================== TRIGGER SYSCALL ===========================*/ | |
// ... | |
/*=============================== ASSERT PARAMETERS ===========================*/ | |
/* Parameter 1: res (type: PT_ERRNO) */ | |
evt_test->assert_numeric_param(1, (int64_t)errno_value); | |
/* Parameter 2: data (type: PT_BYTEBUF) */ | |
evt_test->assert_empty_param(2); | |
/*=============================== ASSERT PARAMETERS ===========================*/ | |
// ... | |
} | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for the suggestion ;) I will try to create version 2 of this document ASAP :)
I will move the content of this document into a new Blog post or merge it into an existing one falcosecurity/falco-website#766 so we can close this :) |
What type of PR is this?
/kind documentation
Any specific area of the project related to this PR?
/area driver-modern-bpf
Does this PR require a change in the driver versions?
No
What this PR does / why we need it:
This PR is a first attempt to explain how to add an already existing syscall into the modern bpf probe. I think this document is quite detailed but obviously, it cannot cover all the corner cases :/ Let me know what you think about that :)
Which issue(s) this PR fixes:
Special notes for your reviewer:
Does this PR introduce a user-facing change?: