Page MenuHomeFreeBSD

Properly do a deep copy of the ioctls capability array for fget_cap().
ClosedPublic

Authored by jhb on Apr 11 2018, 11:36 PM.
Tags
None
Referenced Files
Unknown Object (File)
Oct 1 2024, 11:03 AM
Unknown Object (File)
Sep 18 2024, 5:03 PM
Unknown Object (File)
Sep 17 2024, 5:55 AM
Unknown Object (File)
Sep 14 2024, 7:38 AM
Unknown Object (File)
Sep 9 2024, 2:41 AM
Unknown Object (File)
Sep 8 2024, 11:21 AM
Unknown Object (File)
Sep 8 2024, 6:00 AM
Unknown Object (File)
Sep 6 2024, 8:00 PM
Subscribers

Details

Summary

fget_cap() tries to do a cheaper snapshot of a file descriptor without
holding the file descriptor lock. This snapshot does not do a deep copy
of the ioctls capability array, but instead uses a different return value
to inform the caller to retry the copy with the lock held. However,
filecaps_copy() was returning 1 to indicate that a retry was required,
and fget_cap() was checking for 0 (actually '!filecaps_copy()'). As
a result, fget_cap() did not do a deep copy of the ioctls array and
just reused the original pointer. This cause multiple file descriptor
entries to think they owned the same pointer and eventually resulted in
duplicate frees.

The only code path that I'm aware of that triggers this is to create a
listen socket that has a restricted list of ioctls and then call accept()
which calls fget_cap() with a valid filecaps structure from getsock_cap().

To fix, change the return value of filecaps_copy() to return true if it
succeeds in copying the caps and false if it fails because the lock is
required. I find this more intuitive than fixing the caller in this case.
While here, change the return type from 'int' to 'bool'.

Finally, make filecaps_copy() more robust in the failure case by not
copying any of the source filecaps structure over. This avoids the
possibility of leaking a pointer into a structure if a similar future
caller doesn't properly handle the return value from filecaps_copy().

I also added a test case that panics before this change and now passes.

Test Plan
  • Run included test case.

Diff Detail

Repository
rS FreeBSD src repository - subversion
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

mjg requested changes to this revision.Apr 12 2018, 1:55 AM
mjg added inline comments.
sys/kern/kern_descrip.c
1503 ↗(On Diff #41385)

I think this avoidably extends the branchfest. Instead we can retain *dst = *src and just NULL out the dst->fc_ioctls pointer. Later on copying is needed it is being overwritten anyway.

No opinion about rętval changes.

Still, weird the regular capsicum tests did not run into this.

This revision now requires changes to proceed.Apr 12 2018, 1:55 AM
sys/kern/kern_descrip.c
1503 ↗(On Diff #41385)

I considered that (adding the NULL), but it would mean triggering an assertion later in filecaps_validate() (OTOH, we probably shouldn't be using the returned filecaps anyway in the 'false' case). That said, I do think there is a tradeoff between robustness / clarity and performance. I'm not sure losing one extra branch is worth leaving the filecaps in a weird state. We could perhaps just do filecaps_init() before returning false and clear the entire thing. Seems clearer to avoid changing it at all though.

This looks fine to me.

BTW, why does fget_cap() has the code fragment (under #ifdef CAPABILITIES) inside the local vars block ?

I think the #ifdef just makes it look like the code is in the middle of the block. I think it would be clearer to read if the #ifdef were reversed so that the CAPSICUM code came first as then you'd have the rest of the vars right after the #ifdef and only a bit of code in the #else.

sys/kern/kern_descrip.c
1503 ↗(On Diff #41385)

It's not going to get into weird state. If the is held, we always get the stable copy by definition as nobody can modify caps. If the lock is not held, the caller is supposed to verify the sequence counter which fget_cap is doing. If the sequence counter check says it's fine, the whole object was read correctly (otherwise retries).

sys/kern/kern_descrip.c
1503 ↗(On Diff #41385)

If the caller isn't buggy. In this case the caller was and the inconsistent state was used. If we set it to NULL it would just be a different inconsistent state if a future caller is buggy and returns it.

This revision was not accepted when it landed; it landed in state Needs Revision.Apr 17 2018, 6:07 PM
This revision was automatically updated to reflect the committed changes.