-
Notifications
You must be signed in to change notification settings - Fork 389
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
Add dynamic extraction of a parameter attribute #3143
base: main
Are you sure you want to change the base?
Add dynamic extraction of a parameter attribute #3143
Conversation
9a9d21f
to
ce33ec7
Compare
✅ Deploy Preview for tetragon ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
ce33ec7
to
5ebb3e0
Compare
e3117d7
to
ba048b3
Compare
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.
I like it, left some comments
thanks for splitting the code in multiple logical changes, it's rare ;-)
we were originally thinking of using C expression parsing (there's some go lib for that) but I think that can be done later if it's ever needed, the basic parsing you did should be fine
please add tests, would be great to have some framework where we could easily add various 'ExtractParam' expressions for testing
ba048b3
to
1fb7dbc
Compare
753fd10
to
2b1153f
Compare
2b1153f
to
a04e634
Compare
058c828
to
391c5a8
Compare
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.
looks great, I left some comments, thanks
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.
I've been pulled-in for the docs change, here's a review. Thanks for adding docs!
391c5a8
to
dac4d2e
Compare
@olsajiri The CI tests are still crashing on all kernel versions. I ran my tests on a 6.6 kernel, and everything worked fine. I'll investigate further to figure out the issue and update you soon. EDIT : The tests were failing because EDIT 2 : CI fails on |
e279770
to
3a85916
Compare
Hi, This feature is amazing, thanks for implementing it as it will open up usage of all the security_path_* kprobes that have dentry arguments, which is something I have been waiting for 😄 |
I think @kkourt wanted to check that before merging |
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! This PR looks fantastic!
Please find some comments below. Other than a few minor things, I think we are good to merge this. Some of the comments refer to additional work, but I would suggest to leave this as follow-ups since the PR is already a lot of commits.
const EventConfigMaxArgs = 5 | ||
const MaxBtfArgDepth = 10 // Artificial value for compilation, may be extended |
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.
Not sure what you mean by "artificial" here. Shouldn't this value be the same as "MAX_BTF_ARG_DEPTH" from bpf side?
return 1; | ||
*data->arg = *data->arg + data->btf_config[i].offset; | ||
if (data->btf_config[i].is_pointer) | ||
probe_read((void *)data->arg, sizeof(char *), (void *)*data->arg); |
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.
Note sure how this works if ->is_pointer
is false. Can you please provide an example/explanation in the commit message or in a comment?
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.
- When accessing
task_struct->comm
it is not needed sincecomm
is not a pointer
https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/sched.h#L1138:
char comm[TASK_COMM_LEN];
I'm sorry, but I still don't understand how things are supposed to work without pointers.
Can you provide an example of a hook and a "resolve:" field?
(*btfArgs)[i].IsInitialized = uint16(1) | ||
return ResolveBtfPath(btfArgs, t.Target, pathToFound, i+1) | ||
} | ||
(*btfArgs)[i-1].IsPointer = uint16(1) |
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.
Why is the above line needed?
As I commented in the bpf code, it's not clear to me what it means to have IsPonter
equal to false
but IsIntialized
set to true
.
@@ -216,6 +216,8 @@ func processMembers( | |||
if t, ok := currentType.(*btf.Pointer); ok { | |||
(*btfArgs)[i].IsPointer = uint16(1) | |||
currentType = t.Target | |||
} else if _, ok := currentType.(*btf.Int); ok { | |||
(*btfArgs)[i].IsPointer = uint16(1) |
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.
Any reason to not fold in this patch with the original version?
// https://lore.kernel.org/bpf/[email protected]/ | ||
return errFn(fmt.Errorf("Error: LSM programs could not use Resolve flag for your kernel version." + | ||
"Please use Kprobe instead or update your kernel to version 5.8 or higher")) | ||
} |
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.
Checking kernel versions does not always work as expected because there are kernels with older versions that backport a lot of functionality.
Am I correct to assume that the resolveBtfArg
would fail anyway? If so, I would suggest something like:
lastBtfType, btfArg, err := resolveBtfArg("bpf_lsm_"+f.Hook, a)
if err != nil {
return errFn(fmt.Errorf("could not resolve path from btf. You can try to use a kprobe or update your kernel version to 5.8 or later"))
}
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, doc addition now looks good :)
9d7f6b6
to
3624cef
Compare
80f792c
to
4f714e2
Compare
This commit introduces the `struct config_btf_arg_depth`. It appends `btf_argN` to `struct event_config` as an array. This array stores the path to the searched data. Any `btf_argN` can have a list of elements as follow : file->f_path is 152 bytes, so the array will look like [{ offset: 152, is_pointer: 0, is_initialized: 1 }, ...]. The max value `MAX_BTF_ARG_DEPTH` as been set arbitrary as the verifier need a fixed size. In config_btf_arg, is_pointer and is_initialized are u16 because it must match padding of 64 bits long structure Signed-off-by: Tristan d'Audibert <[email protected]>
…_argN This commit introduces the “extract_arg_depth” function and loops over it to move into the “arg” buffer of config->btf_argN[i]->offset by iteration. The goal is to reach the requiered data by overwriting over arg with the new value. The `is_pointer` field is used to `probe_read` the current field to access the data. If this is false, this means the field does not need such a thing. For instance : - When accessing `linux_binprm->filename` the `filename` field is a pointer, so this means we have to `probe_read` it. (https://elixir.bootlin.com/linux/v4.19.325/source/include/linux/binfmts.h#L57) - When accessing `task_struct->comm` it is not needed since `comm` is not a pointer (https://elixir.bootlin.com/linux/v6.13-rc3/source/include/linux/sched.h#L1138) So when the loop reach that point, in the first case it will first reach the offset and then `probe_read` and on the second, just reach the offset. Signed-off-by: Tristan d'Audibert <[email protected]>
This commit checks if the btf.Type exists in Tetragon existing types. This is useful if we don't want to set the field `type` in the policy. Though this function does not work for many cases For instance: `struct file` with btf is called `file` and also exists in `GenericStringToType` with the same alias. However, the `struct sk_buff` is called `sk_buff` in btf but `skb` in Tetragon. So this type will return `GenericInvalidType`. The same thing happened with `linux_binprm->filename` as the type is `char` and does not exist in Tetragon types. Signed-off-by: Tristan d'Audibert <[email protected]>
ResolveBtfPath function recursively searches in a btf structure in order to find a specific path until it reaches the target or fails. The function also searches in embedded anonymous structures or unions to cover as much use cases as possible. For instance, mm_struct has 2 fields; anonymous struct and another type. But you are still able to look into the anonymous struct by specifying a path like "mm.pgd.pgd". For instance, if the search is in the linux_binprm structure and the path is `file.f_path.dentry.d_name.name`, the following actions will be done. - Look for the variable name `file` inside `linux_binprm`. - If it matches, it stores the offset from linux_binprm where the `file` variable could be found. - Then it takes the btf type `file` and searches for a parameter named `f_path`. The `ResolveNestedTypes` function has for only purpose to bypass types we have no interests in. Since BTF types are nested, we may encounter those "useless" information that we need to ignore. Signed-off-by: Tristan d'Audibert <[email protected]>
In order to read the data properly on BPF side, integer/long values must use `bpf_probe_read`. So now, every time the latest type retrieved is defined as an integer, it will be safely read before accessing the data. Signed-off-by: Tristan d'Audibert <[email protected]>
I've noticed that unit tests load differently the btf file than locally. So when using `NewBTF()` locally it worked, but during the pipeline it crashes. To avoid this behaviour, `LoadBTF` is added and ensure the btf file path is set before trying to load it. Otherwise, it run the `InitCachedBtf()`. Signed-off-by: Tristan d'Audibert <[email protected]>
The function `FindBtfFuncParamFromHook` has been added to identify the exact BTF type of the hook's parameters. This allows us to handle cases where the hook has parameters that include multiple pointers to structures. With those information, we will be able to dereference correctly any parameters until we reach the data Signed-off-by: Tristan d'Audibert <[email protected]>
This commit adds a new parameter to give the ability to the user to search for a specific attribute following a "path" as follow: ```yml ... - hook: "bprm_check_security" args: - index: 0 # index 0 is struct linux_binprm type: "string" resolve: "file.f_path.dentry.d_name.name" ... ``` The above config can be used to extract a specific attribute from the structure at index 0. Signed-off-by: Tristan d'Audibert <[email protected]>
Searches if every user defined type with Resolve exists as a BTF type and stores its corresponding offset in ConfigBtfArg. This function does a basic split on `Resolve` to obtain the "path" to the required data. Then, the array is given to `btf.ResolveBtfPath` to find the offset of each element until we reach the required data. The output is stored in EventConfig to keep the normal behaviour. For example, if the arg 0 is `struct linux_binprm` and Resolve is set to `file.f_path.dentry.d_name.name`, the output will give an array of all the offsets from their parents as follows : [{ offset: 96, is_pointer: 0 }, { offset: 152, is_pointer: 1 }, ...] Signed-off-by: Tristan d'Audibert <[email protected]>
This commit updates `addLsm` function to use the new `arg.Resolve` option in order to look for the attributes in BTF structure. Signed-off-by: Tristan d'Audibert <[email protected]>
Since the Resolve attribute feature uses the hook name to retrieve the hook parameters, if the LSM hook does not exist inside the BTF file, it is not possible to resolve attributes correctly. The feature that allows this was added to the kernel in v5.8 with the patch below: https://lore.kernel.org/bpf/[email protected]/ After this patch, LSM hooks can be retrieved in the BTF file with this format: `bpf_lsm_<hook_name>` PS: The commit has been backported to kernel 5.7, you can check this by running the below command `git branch -r --contains 9d3fdea789c8fab51381c2d609932fabe94c0517` Signed-off-by: Tristan d'Audibert <[email protected]>
As BTF types are not defined for Uprobes, their offsets can't be found in BTF file. Thus, if the user defines Resolve, an error is returned Signed-off-by: Tristan d'Audibert <[email protected]>
Add very similar code as in `genericlsm.go` file to handle Resolve feature. Signed-off-by: Tristan d'Audibert <[email protected]>
…licy `TestResolveBtfArgFromKprobePolicy` is introduced. It aims to test the access to the requested attribute with no errors and assert that the very last type corresponds to the requested policy type (string/int/etc). 3 different cases are handled: - Test a classic hook - Test resolve from double pointer parameter - Assert an error is raised when the resolve path is too long Signed-off-by: Tristan d'Audibert <[email protected]>
This test ensures 4 things : - It can retrieve correctly the parameter of an kprobe hook. - Is raises an error when btf.Type could not be found - It raises an error when the type found is not a function. - It raises an error when the requested parameter index is out of range for the FuncProto.Params array. Signed-off-by: Tristan d'Audibert <[email protected]>
This commit adds 3 tests for ResolveBtfPath algorithm : - `testAssertEqualBtfPath` to assert that a specific path has the exact same btfConfig as expected. - `testAssertPathIsAccessible` tries to reach the path and asserts that no errors are raised. - `testAssertErrorOnInvalidPath` asserts that the error messages raised if the path is incorrect. The chosen test cases have embed union/anonymous structs. To Test locally : ``` go test -exec "sudo" ./pkg/btf/ ``` Signed-off-by: Tristan d'Audibert <[email protected]>
Signed-off-by: Tristan d'Audibert <[email protected]>
Signed-off-by: Tristan d'Audibert <[email protected]>
4f714e2
to
41783b6
Compare
I rebased from main to fix the CI issues after GitHub update (resolved with #3387), but it seems |
The discussion for this PR can be found here #3142
Take this PR in the today state as a proof of concept for dynamic parameter extraction. I will continue to work on this PR until the below checks are done.
At the current state, the PR is able to
Description
This PR introduce the dynamic extraction of a custom attribute
Test the PR
You can use the following config
Release-note