Mercurial > hg > nginx
comparison src/core/ngx_resolver.c @ 6351:497d0cff8ace
Resolver: fixed use-after-free memory accesses with CNAME.
When several requests were waiting for a response, then after getting
a CNAME response only the last request's context had the name updated.
Contexts of other requests had the wrong name. This name was used by
ngx_resolve_name_done() to find the node to remove the request context
from. When the name was wrong, the request could not be properly
cancelled, its context was freed but stayed linked to the node's waiting
list. This happened e.g. when the first request was aborted or timed
out before the resolving completed. When it completed, this triggered
a use-after-free memory access by calling ctx->handler of already freed
request context. The bug manifests itself by
"could not cancel <name> resolving" alerts in error_log.
When a request was responded with a CNAME, the request context kept
the pointer to the original node's rn->u.cname. If the original node
expired before the resolving timed out or completed with an error,
this would trigger a use-after-free memory access via ctx->name in
ctx->handler().
The fix is to keep ctx->name unmodified. The name from context
is no longer used by ngx_resolve_name_done(). Instead, we now keep
the pointer to resolver node to which this request is linked.
Keeping the original name intact also improves logging.
author | Roman Arutyunyan <arut@nginx.com> |
---|---|
date | Tue, 26 Jan 2016 16:46:59 +0300 |
parents | a5767988c022 |
children | ff9b32c0e141 |
comparison
equal
deleted
inserted
replaced
6350:a5767988c022 | 6351:497d0cff8ace |
---|---|
57 | 57 |
58 | 58 |
59 static void ngx_resolver_cleanup(void *data); | 59 static void ngx_resolver_cleanup(void *data); |
60 static void ngx_resolver_cleanup_tree(ngx_resolver_t *r, ngx_rbtree_t *tree); | 60 static void ngx_resolver_cleanup_tree(ngx_resolver_t *r, ngx_rbtree_t *tree); |
61 static ngx_int_t ngx_resolve_name_locked(ngx_resolver_t *r, | 61 static ngx_int_t ngx_resolve_name_locked(ngx_resolver_t *r, |
62 ngx_resolver_ctx_t *ctx); | 62 ngx_resolver_ctx_t *ctx, ngx_str_t *name); |
63 static void ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree, | 63 static void ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree, |
64 ngx_queue_t *queue); | 64 ngx_queue_t *queue); |
65 static ngx_int_t ngx_resolver_send_query(ngx_resolver_t *r, | 65 static ngx_int_t ngx_resolver_send_query(ngx_resolver_t *r, |
66 ngx_resolver_node_t *rn); | 66 ngx_resolver_node_t *rn); |
67 static ngx_int_t ngx_resolver_create_name_query(ngx_resolver_t *r, | 67 static ngx_int_t ngx_resolver_create_name_query(ngx_resolver_t *r, |
374 return NGX_OK; | 374 return NGX_OK; |
375 } | 375 } |
376 | 376 |
377 /* lock name mutex */ | 377 /* lock name mutex */ |
378 | 378 |
379 rc = ngx_resolve_name_locked(r, ctx); | 379 rc = ngx_resolve_name_locked(r, ctx, &ctx->name); |
380 | 380 |
381 if (rc == NGX_OK) { | 381 if (rc == NGX_OK) { |
382 return NGX_OK; | 382 return NGX_OK; |
383 } | 383 } |
384 | 384 |
401 | 401 |
402 | 402 |
403 void | 403 void |
404 ngx_resolve_name_done(ngx_resolver_ctx_t *ctx) | 404 ngx_resolve_name_done(ngx_resolver_ctx_t *ctx) |
405 { | 405 { |
406 uint32_t hash; | |
407 ngx_resolver_t *r; | 406 ngx_resolver_t *r; |
408 ngx_resolver_ctx_t *w, **p; | 407 ngx_resolver_ctx_t *w, **p; |
409 ngx_resolver_node_t *rn; | 408 ngx_resolver_node_t *rn; |
410 | 409 |
411 r = ctx->resolver; | 410 r = ctx->resolver; |
423 | 422 |
424 /* lock name mutex */ | 423 /* lock name mutex */ |
425 | 424 |
426 if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) { | 425 if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) { |
427 | 426 |
428 hash = ngx_crc32_short(ctx->name.data, ctx->name.len); | 427 rn = ctx->node; |
429 | |
430 rn = ngx_resolver_lookup_name(r, &ctx->name, hash); | |
431 | 428 |
432 if (rn) { | 429 if (rn) { |
433 p = &rn->waiting; | 430 p = &rn->waiting; |
434 w = rn->waiting; | 431 w = rn->waiting; |
435 | 432 |
470 } | 467 } |
471 } | 468 } |
472 | 469 |
473 | 470 |
474 static ngx_int_t | 471 static ngx_int_t |
475 ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx) | 472 ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx, |
473 ngx_str_t *name) | |
476 { | 474 { |
477 uint32_t hash; | 475 uint32_t hash; |
478 ngx_int_t rc; | 476 ngx_int_t rc; |
477 ngx_str_t cname; | |
479 ngx_uint_t naddrs; | 478 ngx_uint_t naddrs; |
480 ngx_addr_t *addrs; | 479 ngx_addr_t *addrs; |
481 ngx_resolver_ctx_t *next, *last; | 480 ngx_resolver_ctx_t *next, *last; |
482 ngx_resolver_node_t *rn; | 481 ngx_resolver_node_t *rn; |
483 | 482 |
484 ngx_strlow(ctx->name.data, ctx->name.data, ctx->name.len); | 483 ngx_strlow(name->data, name->data, name->len); |
485 | 484 |
486 hash = ngx_crc32_short(ctx->name.data, ctx->name.len); | 485 hash = ngx_crc32_short(name->data, name->len); |
487 | 486 |
488 rn = ngx_resolver_lookup_name(r, &ctx->name, hash); | 487 rn = ngx_resolver_lookup_name(r, name, hash); |
489 | 488 |
490 if (rn) { | 489 if (rn) { |
491 | 490 |
492 /* ctx can be a list after NGX_RESOLVE_CNAME */ | 491 /* ctx can be a list after NGX_RESOLVE_CNAME */ |
493 for (last = ctx; last->next; last = last->next); | 492 for (last = ctx; last->next; last = last->next); |
557 | 556 |
558 /* NGX_RESOLVE_CNAME */ | 557 /* NGX_RESOLVE_CNAME */ |
559 | 558 |
560 if (ctx->recursion++ < NGX_RESOLVER_MAX_RECURSION) { | 559 if (ctx->recursion++ < NGX_RESOLVER_MAX_RECURSION) { |
561 | 560 |
562 ctx->name.len = rn->cnlen; | 561 cname.len = rn->cnlen; |
563 ctx->name.data = rn->u.cname; | 562 cname.data = rn->u.cname; |
564 | 563 |
565 return ngx_resolve_name_locked(r, ctx); | 564 return ngx_resolve_name_locked(r, ctx, &cname); |
566 } | 565 } |
567 | 566 |
568 last->next = rn->waiting; | 567 last->next = rn->waiting; |
569 rn->waiting = NULL; | 568 rn->waiting = NULL; |
570 | 569 |
600 | 599 |
601 last->next = rn->waiting; | 600 last->next = rn->waiting; |
602 rn->waiting = ctx; | 601 rn->waiting = ctx; |
603 ctx->state = NGX_AGAIN; | 602 ctx->state = NGX_AGAIN; |
604 | 603 |
604 do { | |
605 ctx->node = rn; | |
606 ctx = ctx->next; | |
607 } while (ctx); | |
608 | |
605 return NGX_AGAIN; | 609 return NGX_AGAIN; |
606 } | 610 } |
607 | 611 |
608 ngx_queue_remove(&rn->queue); | 612 ngx_queue_remove(&rn->queue); |
609 | 613 |
638 rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t)); | 642 rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t)); |
639 if (rn == NULL) { | 643 if (rn == NULL) { |
640 return NGX_ERROR; | 644 return NGX_ERROR; |
641 } | 645 } |
642 | 646 |
643 rn->name = ngx_resolver_dup(r, ctx->name.data, ctx->name.len); | 647 rn->name = ngx_resolver_dup(r, name->data, name->len); |
644 if (rn->name == NULL) { | 648 if (rn->name == NULL) { |
645 ngx_resolver_free(r, rn); | 649 ngx_resolver_free(r, rn); |
646 return NGX_ERROR; | 650 return NGX_ERROR; |
647 } | 651 } |
648 | 652 |
649 rn->node.key = hash; | 653 rn->node.key = hash; |
650 rn->nlen = (u_short) ctx->name.len; | 654 rn->nlen = (u_short) name->len; |
651 rn->query = NULL; | 655 rn->query = NULL; |
652 #if (NGX_HAVE_INET6) | 656 #if (NGX_HAVE_INET6) |
653 rn->query6 = NULL; | 657 rn->query6 = NULL; |
654 #endif | 658 #endif |
655 | 659 |
656 ngx_rbtree_insert(&r->name_rbtree, &rn->node); | 660 ngx_rbtree_insert(&r->name_rbtree, &rn->node); |
657 } | 661 } |
658 | 662 |
659 rc = ngx_resolver_create_name_query(r, rn, &ctx->name); | 663 rc = ngx_resolver_create_name_query(r, rn, name); |
660 | 664 |
661 if (rc == NGX_ERROR) { | 665 if (rc == NGX_ERROR) { |
662 goto failed; | 666 goto failed; |
663 } | 667 } |
664 | 668 |
717 rn->valid = 0; | 721 rn->valid = 0; |
718 rn->ttl = NGX_MAX_UINT32_VALUE; | 722 rn->ttl = NGX_MAX_UINT32_VALUE; |
719 rn->waiting = ctx; | 723 rn->waiting = ctx; |
720 | 724 |
721 ctx->state = NGX_AGAIN; | 725 ctx->state = NGX_AGAIN; |
726 | |
727 do { | |
728 ctx->node = rn; | |
729 ctx = ctx->next; | |
730 } while (ctx); | |
722 | 731 |
723 return NGX_AGAIN; | 732 return NGX_AGAIN; |
724 | 733 |
725 failed: | 734 failed: |
726 | 735 |
840 ngx_add_timer(ctx->event, ctx->timeout); | 849 ngx_add_timer(ctx->event, ctx->timeout); |
841 | 850 |
842 ctx->next = rn->waiting; | 851 ctx->next = rn->waiting; |
843 rn->waiting = ctx; | 852 rn->waiting = ctx; |
844 ctx->state = NGX_AGAIN; | 853 ctx->state = NGX_AGAIN; |
854 ctx->node = rn; | |
845 | 855 |
846 /* unlock addr mutex */ | 856 /* unlock addr mutex */ |
847 | 857 |
848 return NGX_OK; | 858 return NGX_OK; |
849 } | 859 } |
925 rn->waiting = ctx; | 935 rn->waiting = ctx; |
926 | 936 |
927 /* unlock addr mutex */ | 937 /* unlock addr mutex */ |
928 | 938 |
929 ctx->state = NGX_AGAIN; | 939 ctx->state = NGX_AGAIN; |
940 ctx->node = rn; | |
930 | 941 |
931 return NGX_OK; | 942 return NGX_OK; |
932 | 943 |
933 failed: | 944 failed: |
934 | 945 |
955 | 966 |
956 | 967 |
957 void | 968 void |
958 ngx_resolve_addr_done(ngx_resolver_ctx_t *ctx) | 969 ngx_resolve_addr_done(ngx_resolver_ctx_t *ctx) |
959 { | 970 { |
960 in_addr_t addr; | |
961 ngx_queue_t *expire_queue; | 971 ngx_queue_t *expire_queue; |
962 ngx_rbtree_t *tree; | 972 ngx_rbtree_t *tree; |
963 ngx_resolver_t *r; | 973 ngx_resolver_t *r; |
964 ngx_resolver_ctx_t *w, **p; | 974 ngx_resolver_ctx_t *w, **p; |
965 struct sockaddr_in *sin; | |
966 ngx_resolver_node_t *rn; | 975 ngx_resolver_node_t *rn; |
967 #if (NGX_HAVE_INET6) | |
968 uint32_t hash; | |
969 struct sockaddr_in6 *sin6; | |
970 #endif | |
971 | 976 |
972 r = ctx->resolver; | 977 r = ctx->resolver; |
973 | 978 |
974 switch (ctx->addr.sockaddr->sa_family) { | 979 switch (ctx->addr.sockaddr->sa_family) { |
975 | 980 |
994 | 999 |
995 /* lock addr mutex */ | 1000 /* lock addr mutex */ |
996 | 1001 |
997 if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) { | 1002 if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) { |
998 | 1003 |
999 switch (ctx->addr.sockaddr->sa_family) { | 1004 rn = ctx->node; |
1000 | |
1001 #if (NGX_HAVE_INET6) | |
1002 case AF_INET6: | |
1003 sin6 = (struct sockaddr_in6 *) ctx->addr.sockaddr; | |
1004 hash = ngx_crc32_short(sin6->sin6_addr.s6_addr, 16); | |
1005 rn = ngx_resolver_lookup_addr6(r, &sin6->sin6_addr, hash); | |
1006 break; | |
1007 #endif | |
1008 | |
1009 default: /* AF_INET */ | |
1010 sin = (struct sockaddr_in *) ctx->addr.sockaddr; | |
1011 addr = ntohl(sin->sin_addr.s_addr); | |
1012 rn = ngx_resolver_lookup_addr(r, addr); | |
1013 } | |
1014 | 1005 |
1015 if (rn) { | 1006 if (rn) { |
1016 p = &rn->waiting; | 1007 p = &rn->waiting; |
1017 w = rn->waiting; | 1008 w = rn->waiting; |
1018 | 1009 |
2012 | 2003 |
2013 ctx = rn->waiting; | 2004 ctx = rn->waiting; |
2014 rn->waiting = NULL; | 2005 rn->waiting = NULL; |
2015 | 2006 |
2016 if (ctx) { | 2007 if (ctx) { |
2017 ctx->name = name; | 2008 |
2018 | 2009 for (next = ctx; next; next = next->next) { |
2019 (void) ngx_resolve_name_locked(r, ctx); | 2010 next->node = NULL; |
2011 } | |
2012 | |
2013 (void) ngx_resolve_name_locked(r, ctx, &name); | |
2020 } | 2014 } |
2021 | 2015 |
2022 ngx_resolver_free(r, rn->query); | 2016 ngx_resolver_free(r, rn->query); |
2023 rn->query = NULL; | 2017 rn->query = NULL; |
2024 #if (NGX_HAVE_INET6) | 2018 #if (NGX_HAVE_INET6) |