Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@i110
Copy link
Contributor

@i110 i110 commented Feb 19, 2019

How to reproduce

build h2o with ASAN, and run with the following configuration

mruby.handler: |
  proc {|env|
    req = http_request("http://example.com")
    [200, {}, []]
  }

and do h2load -n 100000 -c 100 http://127.0.0.1:8080, then you may see heap-use-after-free bug

How the problem occur

  1. proc execution finishes and req loses its reference
  2. httpclient callback (e.g. on_head) is called
  3. in the callback function , mrb_obj_alloc is called (e.g. mrb_str_new)
  4. incremental gc starts and req gets released
  5. dispose_context function is called, the httpclient and the context is freed
  6. touch freed memory in the same callback function

In current design, 5. happens because h2o_mruby_http_request_ctx_t's lifetime relies on mruby objects (I think it's reasonable itself)

How to fix

I added the following two amendments:

  • a) putting req object into gc arena (i.e. mrb_gc_protect(ctx->refs.request)) in httpclient callback function
  • b) check whether the object are alive or not using mrb_object_dead_p function, and if it's not, abort immediately

why only a) is not enough?

When httpclient callback is fired: if the incremental gc state (mrb->gc.state) is MRB_GC_STATE_SWEEP, mrb_gc_protect and mrb_gc_register don't protect objects from gc at all, because the gc arena and the global variable table are marked only in root scan phase and the end of marking phase. But here we can perfectly know whether the objects are alive or not because sweep phase means that all incremental marking is finished in this gc cycle. If they are dead, we don't have to do anything and even be able to actively dispose the context and httpclient immediately.

why only b) is not enough?

When httpclient callback is fired: if the incremental gc state is MRB_GC_STATE_MARK, the objects can be recognized as dead in later incremental marking. So we have to keep that objects by putting them into the arena.

ctx->client = NULL;
dispose_context(ctx);
return NULL;
}
Copy link
Member

Choose a reason for hiding this comment

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

I see this code repeated lots of times, wonder if we can avoid repeating the same thing.

For example, would it make sense to do something like:

if (!context_is_alive_or_dispose(ctx))
    return NULL;

while defining context_is_alive_or_dispose as a function that checks if the context is alive, and if not, disposes it?

Admittedly, the name sounds terse (we should preferably choose a better one), but IMO being terse is better than having the same logic copy-pasted.

Copy link
Contributor Author

@i110 i110 Feb 27, 2019

Choose a reason for hiding this comment

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

Thank you for the comment. I did it in bc64151

@i110 i110 force-pushed the i110/mruby-http-request-lifecycle branch from 90e24bf to bc64151 Compare February 27, 2019 05:31
Copy link
Member

@kazuho kazuho left a comment

Choose a reason for hiding this comment

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

Thank you for the changes. LGTM. Please feel free to merge when you think it's ready.

@i110 i110 merged commit ed9f030 into master Feb 28, 2019
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.

3 participants