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

fix possible use-after-free in chained future implementation #5927

Merged
merged 5 commits into from
May 2, 2024

Conversation

grondo
Copy link
Contributor

@grondo grondo commented May 1, 2024

This PR fixes the problem with chained futures described in #5923: if a next future in a chain is destroyed before the prev future, then a segfault due to use-after-free could occur later if prev is eventually fulfilled since this references next.
The case where this is likely to occur is when flux_future_then(3) is called with a timeout on a next future, since the timeout will fulfill next with an error without even touching the prev future.

The fix is not exactly elegant. A reference to the chained_future object is placed in the next future's aux list, so that when it is destroyed it can (optionally) destroy prev. However, this needs to be avoided in the common case where prev is destroyed before next, so an aux_item destructor is also placed in prev to nullify cf->prev so that future is not double freed. Finally, since the underlying chained_future object is now referenced in multiple places, and these are not called in any guaranteed order, that structure gets a reference counter and a reference count of 3:

  1. for the presence in prev aux_item list as flux::chained
  2. for the anonymous linkage in next aux_item list
  3. for the anonymous linkage in prev aux_item list to call nullify_prev

A test is added and was tested under valgrind etc.

Copy link
Member

@garlick garlick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent!

@@ -432,6 +442,18 @@ static void chained_future_init (flux_future_t *f, void *arg)
fulfill_next (f, cf->next);
}

void cf_next_destroy (struct chained_future *cf)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make static?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh, good catch 👍

grondo added 5 commits May 2, 2024 10:13
Problem: There is trailing whitespace and other formatting in
src/common/libflux/composite_future.c that does not meet current
project norms.

Remove trailing whitespace.

Split long function arguments and conditionals one per line where
necessary.
Problem: There is redundant code in chained_future_create() when
the constructor is called for the same future twice, e.g. when using
flux_future_and_then(3) _and_ flux_future_or_then(3).

Just skip the redundant code by returning early if the chained future
aux item is already present in the target future.
Problem: If the next future in a chained future is destroyed before
the previous future, then a use-after-free can occur if the previous
future is eventually fulfilled, since chained_continuation() and/or
flux_future_continue(3) reference the now destroyed cf->next. This
condition can occur, for example, if a timeout is specified in a call
to flux_future_then(3) on cf->next, but can also occur in cases where
cf->next is destroyed early for other reasons.

Additionally, a reasonable expectation is that when next is destroyed,
this prevents prev (and any other previous futures) callbacks from being
called, either by destroying them or by other means.

Arrange to notify the chained_future object when cf->next is destroyed
by using an aux_item destructor. Destroy cf->prev if it is non-NULL.

Ensure cf->prev is not subject to double-free by nullifying it when
cf->prev is destroyed. This allows cf->prev and cf->next to be destroyed
in any order safely.

Since cf is associated in the aux_item list of cf->prev, and it is now
referenced by cf->next as well, add a refcount to this object and take
a reference for both cf->prev and cf->next. When both references are
released the object will be destroyed.

Fixes flux-framework#5923
Problem: A function in test/composite_future.c is called
flux_future_timeout() which is confusing since this is not an exported
libflux symbol.

Rename the function simply future_timeout().
Problem: No tests in the testsuite ensure that there is not a
use-after-free when the next future in a chain is destroyed before
the previous if fulfilled.

Add a repoducer to the composite_future unit tests.
@grondo
Copy link
Contributor Author

grondo commented May 2, 2024

Ok, fixed the non-static function and will set MWP. Thanks!

Copy link

codecov bot commented May 2, 2024

Codecov Report

Attention: Patch coverage is 75.67568% with 9 lines in your changes are missing coverage. Please review.

Project coverage is 83.34%. Comparing base (86d905f) to head (dcd5de6).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #5927   +/-   ##
=======================================
  Coverage   83.34%   83.34%           
=======================================
  Files         514      514           
  Lines       82974    83001   +27     
=======================================
+ Hits        69158    69181   +23     
- Misses      13816    13820    +4     
Files Coverage Δ
src/common/libflux/composite_future.c 81.81% <75.67%> (-0.54%) ⬇️

... and 8 files with indirect coverage changes

@mergify mergify bot merged commit 16cefe3 into flux-framework:master May 2, 2024
34 of 35 checks passed
@grondo grondo deleted the issue#5923 branch May 2, 2024 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants