qmonnet / rbpf

Rust virtual machine and JIT compiler for eBPF programs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add ability for caller to replace verification function

jackcmay opened this issue · comments

The verification function is coded directly in rbpf. Providing a way for the caller to override the default verifier with their own would provide a lot of flexibility and less need for others for fork and customize this repository for their own needs

@qmonnet

Have the following changes in mind and I would like to get your opinion on whether you think this is a good addition to this repository.

We are planning to write our own verification function and would like to be able to continue to pull from qmonnet/rbpf rather than fork our own copy and diverge.

  • Change the new() methods to take an optional verification function
  • Add a new api set_verifier() that allows caller to set a verification function

Or, if you want to preserve the existing new API we could add a new new API that takes the verifier. This would end up a lot more noisy in the code but would retain backward compatibility.

Hi jackmay! No problem whatsoever with adding the possibility to plug a new verification function. It would be nice to have the current one extended if you could, but I understand this may not be possible for your needs.

I'm not sure I understood what you propose, do you intend to change/add both new() and set_verifier()? If so, why is set_verifier() not enough? Do you have to set the function at the creation of the machine?

@qmonnet

Thanks for the quick turnaround!

The existing vm new() function takes a program and calls the built-in verifier on it right away. The built-in verifier may be more strict than the users and panic during vm creation.

Adding the set_verifier() seems symmetrical with add_prog() but its not manditory.

Ah, true. I haven't played with it in some time to be honest.

I am not really afraid of changing the API, but slightly more concerned at the idea of having to pass an argument for the verifier at the creation at the VM, because most people won't need it I believe (at least as long as we do not have several options in rbpf for the verifier) and I would as well avoid to make the new() methods more complicated that they are if I can.

The set_verifier() way seems more tempting, it is closer to what we have with register_helper(). Now I think about it we should be able to call the verifier after setting a helper, in case it incurs additional checks to perform for a more advanced verifier. I'm not strictly opposed to changing new(), but what would you think of the following instead:

  • Changing the current verifier (possibly adapt the exec functions) to accept empty programs. So we could create a new VM with e.g. let mut vm = rbpf::EbpfVmMbuff::new(None);
  • Then modifying the VM to shape it as intended with set_verifier(), register_helper(), and of course set_prog().

Would that work for you?

While we're at this, maybe the verifier should not panic, but instead return an error, so a user can actually handle invalid programs easily.

This would be for the current verifier, so that's not exactly the same discussion... But sure, we could change that as well.

Yes, but if we allow custom verifiers and have a built-in one, I'd prefer to have them act the same.

We can hardly have a say in that. It will not work if we change it and people implement their own verifier which panics on errors ;). But I get the idea, if we are to allow custom verifiers it would be better to have something more robust as an example for others. Thanks for the notice. I may try to update that this weekend (I mean just the return error instead of panic, not the whole custom verifier thing), unless someone else wishes to do it.

Of course, we can't pretend others to panic, but we can require verifiers to return a result, which immediately encourages extern code to use it to report errors ;)

This is great, that was the next thing on my list to address. I've opened #30 to track.

Thinking about the custom verifier, just one idea: would that make sense to extend the existing verifier with whatever code you have and that could be generic, and that you could publish of course, and then plug into this verifier instead of completely replacing it? More or less what is done in the kernel for offloading programs, the drivers supporting that implement callbacks used by the verifier to perform additional checks for offload. Do you think that could be a solution for what you need? (Probably not, but I'm just wondering.)

Probably not. We've compiled a BPF program that returns but the 'return' instruction is not literally the last instruction in the section. There may be other examples of this where the user may want to do something outside the realms of a typical case or what Linux kernel may do. My hope is that rbpf can become generic enough to support a wide range of BPF use cases.

@qmonnet I'm responding to your comments in #31 about not wanting to change the new function and force folks to pass None if they are happy with the default verifier. I'm fine with a new new function or something similar that does take the verifier. Is that what you are suggesting? I have a pr ready for review, depending on which direction you prefer I'll update it and submit for review.

(To be fair the verifier should not require the last instruction to be an exit, we can have other instructions after that if at the end they jump back to some other parts that has not been previously executed. Anyway...) Ok I understand.

No I did not suggest adding another new() method. What I proposed instead a couple of days ago was the following: doing something closer to what we have with register_helper().

  • First changing the current verifier (possibly adapt the exec functions) to accept empty programs, so we could create a new VM with e.g. let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();.
  • Then modifying the VM to shape it as intended with set_verifier(), register_helper(), and of course set_prog().
let mut vm = rbpf::EbpfVmNoData::new(None).unwrap();
vm.set_verifier(custom_verifier_function);
vm.register_helper(27, a_helper_function);
// Trying to run or JIT-compile at this step returns some error or panic!(),
// we have not set any program yet.
vm.set_prog(&[
    0xb7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]);
vm.prog_exec();

Awesome, thank you!

Pull request to resolve this issue: #34