Message ID | 20230713023232.1411523-1-memxor@gmail.com (mailing list archive) |
---|---|
Headers | show |
Series | Exceptions - 1/2 | expand |
On Thu, Jul 13, 2023 at 08:02:22AM +0530, Kumar Kartikeya Dwivedi wrote: > This series implements the _first_ part of the runtime and verifier > support needed to enable BPF exceptions. Exceptions thrown from programs > are processed as an immediate exit from the program, which unwinds all > the active stack frames until the main stack frame, and returns to the > BPF program's caller. The ability to perform this unwinding safely > allows the program to test conditions that are always true at runtime > but which the verifier has no visibility into. > > Thus, it also reduces verification effort by safely terminating > redundant paths that can be taken within a program. > > The patches to perform runtime resource cleanup during the > frame-by-frame unwinding will be posted as a follow-up to this set. > > It must be noted that exceptions are not an error handling mechanism for > unlikely runtime conditions, but a way to safely terminate the execution > of a program in presence of conditions that should never occur at > runtime. They are meant to serve higher-level primitives such as program > assertions. Sure, that makes sense. > > As such, a program can only install an exception handler once for the > lifetime of a BPF program, and this handler cannot be changed at > runtime. The purpose of the handler is to simply interpret the cookie > value supplied by the bpf_throw call, and execute user-defined logic > corresponding to it. The primary purpose of allowing a handler is to > control the return value of the program. The default handler returns 0 > when from the program when an exception is thrown. > > Fixing the handler for the lifetime of the program eliminates tricky and > expensive handling in case of runtime changes of the handler callback > when programs begin to nest, where it becomes more complex to save and > restore the active handler at runtime. > > The following kfuncs are introduced: > > // Throw a BPF exception, terminating the execution of the program. > // > // @cookie: The cookie that is passed to the exception callback. Without > // an exception callback set by the user, the programs returns > // 0 when an exception is thrown. > void bpf_throw(u64 cookie); If developers are only supposed to use higher level primitives, then why expose a kfunc for it? The above description makes it sound like this should be an implementation detail. [...] Thanks, Daniel
On Mon, 17 Jul 2023 at 23:46, Daniel Xu <dxu@dxuuu.xyz> wrote: > > On Thu, Jul 13, 2023 at 08:02:22AM +0530, Kumar Kartikeya Dwivedi wrote: > > This series implements the _first_ part of the runtime and verifier > > support needed to enable BPF exceptions. Exceptions thrown from programs > > are processed as an immediate exit from the program, which unwinds all > > the active stack frames until the main stack frame, and returns to the > > BPF program's caller. The ability to perform this unwinding safely > > allows the program to test conditions that are always true at runtime > > but which the verifier has no visibility into. > > > > Thus, it also reduces verification effort by safely terminating > > redundant paths that can be taken within a program. > > > > The patches to perform runtime resource cleanup during the > > frame-by-frame unwinding will be posted as a follow-up to this set. > > > > It must be noted that exceptions are not an error handling mechanism for > > unlikely runtime conditions, but a way to safely terminate the execution > > of a program in presence of conditions that should never occur at > > runtime. They are meant to serve higher-level primitives such as program > > assertions. > > Sure, that makes sense. > > > > > As such, a program can only install an exception handler once for the > > lifetime of a BPF program, and this handler cannot be changed at > > runtime. The purpose of the handler is to simply interpret the cookie > > value supplied by the bpf_throw call, and execute user-defined logic > > corresponding to it. The primary purpose of allowing a handler is to > > control the return value of the program. The default handler returns 0 > > when from the program when an exception is thrown. > > > > Fixing the handler for the lifetime of the program eliminates tricky and > > expensive handling in case of runtime changes of the handler callback > > when programs begin to nest, where it becomes more complex to save and > > restore the active handler at runtime. > > > > The following kfuncs are introduced: > > > > // Throw a BPF exception, terminating the execution of the program. > > // > > // @cookie: The cookie that is passed to the exception callback. Without > > // an exception callback set by the user, the programs returns > > // 0 when an exception is thrown. > > void bpf_throw(u64 cookie); > > If developers are only supposed to use higher level primitives, then why > expose a kfunc for it? The above description makes it sound like this > should be an implementation detail. > I can rephrase, but what I meant to say is that it's not an error handling mechanism. But you _can_ directly call bpf_throw as well when failing a condition that you know is always true. It's not necessary to always use the assert macros. That may not be possible as it requires a lvalue, rvalue pair. If the condition is complicated, e.g. the one below, is totally acceptable, if you know it's always going to be true, but the verifier doesn't: if (data + offset > data_end) bpf_throw(XDP_DROP); This can be from a deeply nested callchain, and it eliminates the need to handle this condition all the way back to the main prog. The primary requirement was for implementing assertions within a program, which when untrue still ensure that the program terminates safely. Typically this would require the user to handle the other case, freeing any resources, and returning from a possibly deep callchain back to the kernel. Testing a condition can be used to update the verifier's knowledge about a particular register. By throwing from the other path where the condition is untrue, it's a way to increase the knowledge of the verifier during its symbolic execution while at the same time preserving the safety guarantees.