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

map,prog,link: verify object type in LoadPinned*() #1581

Merged
merged 3 commits into from
Dec 12, 2024

Conversation

mtardy
Copy link
Member

@mtardy mtardy commented Oct 9, 2024

Fixes #1566.

Before this patch, ebpf.LoadPinnedMap and ebpf.LoadPinnedProg both succeed when used with the wrong object type. Meaning that you can open a prog using epbf.LoadPinnedMap or vice-versa and you'll get garbage data in the object's info.

To my knowledge, there are two ways of knowing from an opened FD which object type it is:

  • checking for specific fields in /proc/self/fdinfo/;
  • running readlink(2) on /proc/self/fd/ and check for anon_inode:bpf-prog or anon_inode:bpf-map.

This is a breaking change for users relying on LoadPinned to open any object: I've been using that behavior to scan BPF filesystem.

Note to the reviewer: I've been writing this fairly quickly, I'm sure I'm breaking all your conventions style-wise and I was very hesitant to return an error on failing to run readlink. Please feel free to comment on anything, I just wanted to start with something.

@mejedi
Copy link
Contributor

mejedi commented Oct 11, 2024

It looks like programs with capabilities can’t access their own /proc/self: https://dxuuu.xyz/filecaps.html

I wonder if we can figure a type differently.

@mtardy
Copy link
Member Author

mtardy commented Oct 15, 2024

It looks like programs with capabilities can’t access their own /proc/self: https://dxuuu.xyz/filecaps.html

I wonder if we can figure a type differently.

But isn't c/ebpf already parsing stuff from /proc/self anyway?

@mejedi
Copy link
Contributor

mejedi commented Oct 15, 2024

But isn't c/ebpf already parsing stuff from /proc/self anyway?

I wish it didn't.

It is quite reasonable to run ebpf stuff as non-root user, selectively granting needed capabilities. This will render /proc/self inaccessible.

At the moment, there are two uses of /proc/self — in info.go and internal/linux/vdso.go. The later is quite benign — it is used to determine the kernel version, but the failure is not considered fatal. The library doesn't rely on kernel version for feature detection anyway.

It appears that .Info() on ebpf.Map (but not on ebpf.Program) does not work when run by non-root user with capabilities.

The changes you propose will also prevent access to pinned objects.

I have to admit that I don't understand how to use BPF_OBJ_GET_INFO_BY_FD safely when we aren't sure about the fd type. BPF_OBJ_GET doesn't have a way to specify the expected object type. @dylandreimerink

@dylandreimerink
Copy link
Member

I have to admit that I don't understand how to use BPF_OBJ_GET_INFO_BY_FD safely when we aren't sure about the fd type. BPF_OBJ_GET doesn't have a way to specify the expected object type.

We essentially just get a byte slice back. The trouble is that without knowledge of the BPF object type there is no sure way to know how to decode it properly. We know how big we expect the output to be, but it actually differs by kernel, older kernels will have less fields so a smaller buffer.

So I wouldn't say there is anything inherently unsafe but its currently possible for users to open a pin and get bogus data. It would be nice if we can throw an error telling a user they made a mistake somewhere.

It looks like programs with capabilities can’t access their own /proc/self: https://dxuuu.xyz/filecaps.html

That is very sad. Perhaps we could still add the check, but ignore permission failures to /proc/self. Perhaps some checks are better than no checks? Difficult...

I wonder if we can figure a type differently.

So far we have not figured out another way. It would likely require a kernel change, which isn't a bad idea, but also doesn't help us right now.

We might be able to verify the info we get back from BPF_OBJ_GET_INFO_BY_FD. For example by checking if BTF IDs are plausible or data is written to pointers for name for example. (just an idea at this point)

@mejedi
Copy link
Contributor

mejedi commented Oct 16, 2024

@dylandreimerink
Thank you for sharing your thoughs.

I have to admit that I don't understand how to use BPF_OBJ_GET_INFO_BY_FD safely when we aren't sure about the fd type.

We essentially just get a byte slice back.

Some bytes in the blob we submit are interpreted as a pointer to buffer and other bytes tell the capacity. Imagine we are passing a wrong kind of blob. Can we communicate the capacity wrong and end up with a buffer overrun? Probably not as pointer/capacity fields do not align in different kinds of info structs.

I wonder if we can figure a type differently.

So far we have not figured out another way.

We could probably identify a map by doing lookup_elem with a NULL buffer. To tell if fd is a program, we could add it to a brand new PROG_ARRAY_MAP. Not sure about links. Are there any other kinds of objects that can come from bpffs?

A follow-up commit will make use of ObjType to check the type of pinned
bpf objects before proceeding to wrap a Go type around them.

Signed-off-by: Timo Beckers <timo@isovalent.com>
Signed-off-by: Timo Beckers <timo@isovalent.com>
@ti-mo
Copy link
Collaborator

ti-mo commented Dec 12, 2024

It appears that .Info() on ebpf.Map (but not on ebpf.Program) does not work when run by non-root user with capabilities.

The changes you propose will also prevent access to pinned objects.

Careful taking things (blog posts in particular, even Daniel's 😉) at face value. It's definitely a reasonable assumption to make, and while true for auxv, it doesn't generalize to everything in /proc/self. Here's the output of sudo find -ls of the proc folder of a binary with the following caps: sudo setcap "cap_net_admin,cap_bpf+p". (I'm running with sysctl fs.suid_dumpable=0 here)

   4425307      0 dr-x------   2 root     root            7 Dec 12 12:46 /proc/659947/fd
   4431930      0 lrwx------   1 root     root           64 Dec 12 12:46 /proc/659947/fd/0 -> /dev/pts/1
   4431931      0 l-wx------   1 root     root           64 Dec 12 12:46 /proc/659947/fd/1 -> pipe:[4416412]
...
   4425309      0 dr-xr-xr-x   2 timo     timo            0 Dec 12 12:46 /proc/659947/fdinfo
   4431940      0 -r--r--r--   1 root     root            0 Dec 12 12:46 /proc/659947/fdinfo/0
   4431941      0 -r--r--r--   1 root     root            0 Dec 12 12:46 /proc/659947/fdinfo/1

/proc/self/fd is indeed owned by root, but it turns out readlink works on all nodes in fd/. Symlinks don't carry permissions, so I assume permission checks are done using the underlying object, which is an fd already belonging to the process. I don't think the permissions displayed the symlinks reflect reality; they're also rather inconsistent. I didn't investigate this further.

For fdinfo/ there's no issue, the folder is still owned by the process owner and the nodes within are 0666.

I'll push some changes to this PR shortly.

@ti-mo ti-mo force-pushed the pr/mtardy/check-bpf-type branch from 4a960d5 to e050312 Compare December 12, 2024 12:18
@ti-mo ti-mo changed the title info: error on loading wrong BPF object type map,prog,link: verify object type in LoadPinned*() Dec 12, 2024
@ti-mo ti-mo marked this pull request as ready for review December 12, 2024 12:21
@ti-mo ti-mo requested review from mmat11 and a team as code owners December 12, 2024 12:21
Before this patch, LoadPinned{Map,Prog,Link} all succeeded when called
on a pin of the wrong type, leading to garbage in the object's info.

This patch adds an ObjGetTyped() wrapper around ObjGet() to obtain a file
descriptor's bpf object type. Despite the warnings in
https://dxuuu.xyz/filecaps.html, this works for unprivileged processes with
file caps as well. /proc/self/fd/ contains symlinks whose underlying fds
remain accessible by the user, presumably since they're still owned by the
process owner.

Kernel patches are underway for adding a bpf_type field to ObjGetAttr
to provide future proofing in case new bpf objects are added to the
kernel. The current checks were designed to make this addition as
straightforward as possible.

Signed-off-by: Timo Beckers <timo@isovalent.com>
Co-authored-by: Mahe Tardy <mahe.tardy@gmail.com>
@ti-mo ti-mo force-pushed the pr/mtardy/check-bpf-type branch from e050312 to d4a40af Compare December 12, 2024 12:28
@ti-mo ti-mo merged commit 2f9d785 into cilium:main Dec 12, 2024
17 checks passed
@mtardy mtardy deleted the pr/mtardy/check-bpf-type branch December 12, 2024 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LoadPinned* silently succeeds on objects of wrong type
4 participants