-
Notifications
You must be signed in to change notification settings - Fork 528
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
Protect ACLFilledChecklist heap allocations from leaking #1870
Protect ACLFilledChecklist heap allocations from leaking #1870
Conversation
The common approach when dealing with ACLFilledChecklist non-blocking checks consists of three steps: 1. create an ACLFilledChecklist object on heap 2. configure the object 3. invoke ACLFilledChecklist::nonBlockingCheck() Memory leak occurs if an exception is thrown between (1) and (3). To avoid leaks, whe should rely on RAII mechanism instead of raw pointers.
After introducing ACLFilledChecklist::Make() API the calling code got unique_ptr<> instead of ACLFilledChecklist*, and its semantics remained the same. However, since nonBlockingCheck() still expects a raw pointer (which it takes ownership of), the unique_ptr<> should be released before the call. The new nonBlockingCheck() now encapsulates this requirement.
Also polished new macro description.
There is no difference for the CBDATA_CLASS_WITH_MAKE()-calling class. However, if some future code (incorrectly) creates a child of that class, then we want build to fail. Creating a child requires using something like CBDATA_INTERMEDIATE()/CBDATA_CHILD() for reasons documented in CBDATA_INTERMEDIATE() description. We cannot _guarantee_ that such a build would fail, but making new() private will help in cases where child code calls new(). Besides, tighter restrictions should raise fewer questions, and it is always easier to relax a restriction than to add one.
This change arguably makes this macro-based code slightly easier to grok and clarifies CBDATA_CLASS_WITH_MAKE() intent.
The pointer returned by ACLFilledChecklist::Make() cannot be used for those purposes (because there can be only one checklist/cbdata owner) and, to prevent memory leaks, a different kind of pointer should be used during object configuration.
Also updated stale references to aclCheckFast() and aclNBCheck().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
@@ -2501,7 +2501,7 @@ ConnStateData::postHttpsAccept() | |||
ClientHttpRequest *http = context ? context->http : nullptr; | |||
const char *log_uri = http ? http->log_uri : nullptr; | |||
acl_checklist->syncAle(request, log_uri); | |||
acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, this); | |||
ACLFilledChecklist::NonBlockingCheck(std::move(acl_checklist), httpsSslBumpAccessCheckDone, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explicit std::move() call (and similar calls added in this PR) is one of those exceptional cases where forcing developer to write (and code reader to see) this explicit std::move call is desirable: We want to emphasize that acl_checklist may be gone after this NonBlockingCheck() call returns.
BTW, GCC and clang do not do that for us automatically, but we plan to enhance our CI tests to automatically check Squid code for use-after-move errors.
This comment does not request any PR changes.
acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpNone)); | ||
acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpClientFirst)); | ||
acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpServerFirst)); | ||
fillChecklist(*acl_checklist); | ||
acl_checklist->nonBlockingCheck(httpsSslBumpStep2AccessCheckDone, this); | ||
ACLFilledChecklist::NonBlockingCheck(std::move(acl_checklist), httpsSslBumpStep2AccessCheckDone, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If others prefer, we can simplify this to remove ACLFilledChecklist prefix by making NonBlockingCheck() into a global function:
ACLFilledChecklist::NonBlockingCheck(std::move(acl_checklist), httpsSslBumpStep2AccessCheckDone, this); | |
NonBlockingCheck(std::move(acl_checklist), httpsSslBumpStep2AccessCheckDone, this); |
or
ACLFilledChecklist::NonBlockingCheck(std::move(acl_checklist), httpsSslBumpStep2AccessCheckDone, this); | |
Acl::NonBlockingCheck(std::move(acl_checklist), httpsSslBumpStep2AccessCheckDone, this); |
Due to fairly unique parameter types, such a global function is unlikely to clash with other non-blocking check functions. Please add a comment if you want to see this kind of change (at the cost of adding a corresponding friend
declaration to checklist class type).
This comment, byt itself, does not request any PR changes.
/// Unlike regular Foo::Pointer types, this smart pointer is meant for use | ||
/// during checklist configuration only, when it provides exception safety. | ||
/// Any other/long-term checklist storage requires CbcPointer or equivalent. | ||
using MakingPointer = std::unique_ptr<ACLFilledChecklist>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another possible type name that still addresses the documented "this is not our regular Pointer" concern is UniquePointer:
using MakingPointer = std::unique_ptr<ACLFilledChecklist>; | |
using UniquePointer = std::unique_ptr<ACLFilledChecklist>; |
I prefer MakingPointer because it is more specific to this smart pointer primary purpose, but I acknowledge that "making pointer" is ambiguous.
This comment does not request any PR changes.
Non-blocking ACL checks follow this code pattern: const auto ch = new ACLFilledChecklist(...); fillWithInformation(ch); // may throw ch->nonBlockingCheck(&throwingCallback); // may delete ch or throw! // ch may be a dangling raw pointer here The checklist object is leaked if an exception is thrown by fillWithInformation() (and the code it calls) or by nonBlockingCheck() (and the code it calls, including, in some cases, throwingCallback()). Use std::unique_ptr to avoid such leaks.
…#1870) Non-blocking ACL checks follow this code pattern: const auto ch = new ACLFilledChecklist(...); fillWithInformation(ch); // may throw ch->nonBlockingCheck(&throwingCallback); // may delete ch or throw! // ch may be a dangling raw pointer here The checklist object is leaked if an exception is thrown by fillWithInformation() (and the code it calls) or by nonBlockingCheck() (and the code it calls, including, in some cases, throwingCallback()). Use std::unique_ptr to avoid such leaks.
Non-blocking ACL checks follow this code pattern:
The checklist object is leaked if an exception is thrown by
fillWithInformation() (and the code it calls) or by nonBlockingCheck()
(and the code it calls, including, in some cases, throwingCallback()).
Use std::unique_ptr to avoid such leaks.