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
Alert when a program tries to access one of Tracee's maps #617
Comments
it would be better to add a new event to Tracee-eBPF for monitoring the mapwhich emits the raw fd/names as event arguments. This could be useful for other purposes as well. Then we could write a rule in Tracee-Rules to detect which map was touched by which process. TBD: how tracee-rules knows the names/fds of the maps that Tracee-eBPF created? the PID is written to a file in the output directory. the names of the maps could be written to another file as well. As a followup we could do the same for bpf brograms |
Actually, it's not so trivial how tracee-rules can load this information and pass it to a signature cleanly. probably a better way is for the signature to read the file for itself. for this it would be have to be written in golang |
OBS: I'm not yet doing a PR for this, just keeping a branch in my github account so you can follow. Item 1 and 2
In kernel there are many situations where the real internal function (or syscall, or ioctl, or file_ops handling) is set by an attribute argument, just like: switch (cmd) {
case BPF_MAP_CREATE:
err = map_create(&attr);
break;
case BPF_MAP_LOOKUP_ELEM:
err = map_lookup_elem(&attr);
break;
... What is (or will be) the project's preference for these cases ?
This gives us all the ebpf syscall sub-commands (like PROG_LOAD, OBJ_PIN, ...) events (in a single tracee event), not only the ones touching the map_names... that could be useful for re-utilization like Itay said so. The cons would be we have to interpret the ebpf syscall sub-command type based on the map_cmd variable (with logged data). I think the idea is to keep the ebpf core as generic as it can be, right ?
Item 3
Item 4
|
From what I know, the attribute argument is given as part of the bpf syscall. All of the above cases should go through the lsm hooks when updating a map. Is there a specific case where that is not the case?
Specifically for map there exists security_bpf_map LSM hook. Have you tried to use it instead of security_bpf?
For the security_bpf lsm hooks I would extract the cmd and attr raw values. In a future PR we can interpret these values to the corresponding command name and arguments.
right |
Before answering you, I did some more code reading, including kernels, and I would like to recall the initial theory. I think it could have some inaccurate premises, please advice me if not.
Thinking the 'tampering' surface little further: 1. Would a parallel userland task, creating a BPF MAP be problematic to tracee ?All userland tasks can only manipulate ebpf maps through the bpf syscall and those require a file descriptor for an already created bpf (with BPF_MAP_CREATE). The eBPF bytecode relocations, to access BPF maps created by userland, only occur AFTER userland has created the maps, and userland uses the eBPF ELF metadata to know what maps to create before loading the bpf obj (or after ? not sure). Considering the 'original design' in Interacting with Maps, only if the userland task is able to get the bpf maps file descriptors, created by tracee, either through a unix-domain socket message passing OR through the bpf filesystem (when the map is pinned), it would be able to change maps contents. So, the answer for this question seems to be NO. Example: Probing the bpf syscall LSM hook (e.g. security_bpf_map) would just tell us whoever is creating a BPF map with the same name, but not necessarily trying (close to) to harm tracee application. Let's see: The "security_bpf_map()" kprobe execution path:
would cover sub-commands:
Through BPF_MAP_CREATE we would only get "map_name" (from the initial bpf_attr union in the user headers). The other 2 sub-commands could be more 'dangerous' (I cover this in item (2)).
2. Would a parallel userland code be capable of getting tracee's maps file descriptors ?Getting tracee's maps file descriptors would allow a parallel userland task to tamper tracee's maps.
Independently of the technique used to 'steal' our bpf maps fds - hijacking it between bpf load/bpf map create OR getting it from bpf filesystem OR getting it through fd message passing OR ...) - idea is that we are able to say THIS APP has changed OUR BPF MAP for sure.
Calls to:
would tell us if one is trying to tamper our maps by getting its file descriptors, for example. As long as we have attr.prog_id being used for our maps (and we probably have) we could hook those calls and check if one is trying to get our fds. There are NO other kernel functions calling bpf_map_get_fd_by_id(), which means that the only hook we could have to investigate calls to it would be through 'security_bpf()' one.
There are other bpf commands to be hooked also: the ones that actually CHANGE/CONSULT the maps:
THIS ITEM (2) IS THE CLOSEST TO ISSUE DESCRIPTION AFAICT 3. Would a kernel eBPF running program (any) be capable of accessing our BPF MAPS and changing values ?I don't think so, I would have to research. Nevertheless, the approach of identifying this, if such, would be entirely different as no syscall would be needed. SummaryHow would you like me to proceed for this ? Hook into BPF_MAP_GET_FD_BY_ID and check if one is trying to get our MAPs file descriptors ? Or would you like me to have a "generic PR for bpf syscall sub-commands" even if not really addressing the issue you're describing here ? Looking forwarding to discussing next steps... |
Thanks @rafaeldtinoco for this thorough analysis!
I don't think this will be a problem. As you described, a different task with different maps will have its own fds, and I don't expect a problem from this side.
This is the issue we are trying to solve in this PR
An interesting question that we might want to research for in the future. So let's focus on point number 2. In general, I think that it will be good to have both security_bpf and security_bpf_map events added to tracee. These can be added as raw events, with no detection made. I also want to refer you to a blog which initiated my thoughts about this issue: |
Couldn't play as hard as I wanted yet (libvirt backports and tests all week =() but.. this last commit adds 2 kprobes, like we discussed, and only sends arguments if it makes sense:
and
Since security_bpf_map is also called during bpf(BPF_MAP_CREATE), we can see both behave similarly. In my weekend I'll dig in the prog context id and check BPF_MAP_GET_FD_BY_ID execution path:
to do the detection for get_fd_by_id() like we discussed. Best! -rafaeldtinoco |
In order to have a good test case for the feature, I have played a bit with BPF programs and maps pinning and there is some cool stuff to report at: https://github.com/rafaeldtinoco/portablebpf/blob/hijack/hijack.c This tree has 2 binaries:
Here is the output of mine binary running (based in libbpf skeleton):
And the pinned prog and map:
and the gdb output showing access to the ebpf program info structure and the ebpf map info structure:
name = ksys_sync as you can see.
name = events, as you can see. Through the file descriptors originated from the pinned prog and map, I was able to query kernel internal structure through the bpf() syscall, as expected. THENNNNNNNN, I discovered bpftool sub-commands - which, I confess, I did not know about, as I only used the gen skel command so far. So, instead of doing all tests in C, I can simply use bpftool to query/modify/add/remove key/values in all existing maps, from all existing running eBPF progs...
With tracee-ebpf running:
I can get the comm_filter through its bpf map:
And I can pin whatever I want:
After pinning a map to bpf fs it is even easier to play with the eBPF maps and progs by using libbpf (or directly). The good news is that security_bpf_map event is enough to get map_names and events whenever BPF_OBJ_GET_INFO_BY_FD is given to bpf syscall:
Now, thinking out loud:
Something like:
So, basically tracee would have to save its PID and MAPS names, like initially planned, and tracee-rules signature will do the rest. |
Indeed, bpftool is very powerfull. I'm using it to find "map leaks" when I'm adding new maps to tracee, and it works great.
So, if I understand correctly, security_bpf_map should be enough for us to find map tamepering? Because from your previous note I understood the opposite:
And yes, you are right here:
As descirbed above by @itaysk:
This will require some work, and can be done by a future PR. The current idea that I have in mind is to iterate over tracee's maps in the initBPF function right after |
I have used a systemtap script to play with bpftool and MAPS reading, just to check about what you have asked. When executing
A single SECURITY_BPF() call from the BPF_MAP_GET_NEXT_ID sub-cmd. And then, I start the 'mine' binary, without tracing its bpf() syscalls for loading programs and such, after it is initialized and has its MAP set in kernel, I call:
And we were able to read the MAP name (at very least). The tracing for that call is:
The only security_bpf_map() comes from bpf_map_get_fd_by_id(). So I was able to discover the map by doing: So the call to bpftool generated events in following order:
So my initial statement is more correct in the sense that we can do pretty much everything by probing security_bpf(). Also, if probing security_bpf_map() we would be able to get a call for bpf_map_get_fd_by_id(), the call that gets the file descriptor for a existing MAP, but we would miss other important calls needed to transpass the maps, like BPF_MAP_GET_NEXT_ID (as showed in tracing). |
On your statement:
Yep, I'll leave that part to other PR as you suggested. I think investigation here shows what needs to be done from now on. Should I create a PR for the merge of the 2 events then ? |
Interesting. The question is - to get the map's fd, we have to go thorugh bpf_map_get_fd_by_id(), shouldn't we? And if that is the case, isn't it enough to have security_bpf_map()?
That would be great, thanks! |
Sorry if I did not myself clear before, trying to measure being sufficiently prolix versus not saying enough. You are right, in order to identify some other task getting a FD to one of our maps, a kprobe to security_bpf_map() will be enough. I was also covering the prog/maps list walkthrough as something bad, thus the confusion.
Cool, will do it later today then. |
Here is an idea for how we can know that a map belongs to tracee, and pass it to tracee-rules. |
I think it would work. I like it.
|
Add the following events to tracee-ebpf: - security_bpf: allows bpf() syscall command to be traced. - security_bpf_map: called from bpf() BPF_MAP_GET_FD_BY_ID command. The first is important for generic bpf syscall tracing while the later is triggered whenever some process tries to get an eBPF MAP fd in order to read or update MAP contents: an important feature for tampering prevention. Signed-off-by: Rafael David Tinoco <rafaeldtinoco@gmail.com>
As a security solution, Tracee should protect its own assets from being tampered by another program.
Given a program which has enough privileges (e.g. CAP_SYS_ADMIN or CAP_BPF), it can access and even alter any of Tracee's maps.
Although this kind of attack assumes an attacker which already has dangerous privileges, we can probably mitigate this attack by using one of the following LSM hooks (possibly both):
By attaching to security_bpf_map, we can monitor whenever a non-tracee program tries to access one of our maps and alert when such access happens
The text was updated successfully, but these errors were encountered: