17.09.2020, 09:35, "Ryosuke Niwa" <rn...@webkit.org>:
> Hi all,
>
> I’ve been working with Geoff (ggaren) and Jan Korous from Apple's compiler 
> team to create a static analyzer which detects dangerous use of ref counted 
> objects, and we’re looking for folks who are interested in trying out this 
> tool and helping us refine the tool. Please let me know if you’re interested 
> in using this tool & try applying code changes to WebKit. Our goal is to 
> eventually integrate this tool into buildbot and EWS so that we can catch 
> dangerous code before they get committed.
>
> What is Dangerous?
>
> So what is a dangerous use of ref counted objects you may ask? It’s any use 
> of which we can’t trivially conclude that it doesn’t lead to a use-after-free.
>
> For now, we don’t detect dangerous use of non-ref counted objects including 
> ones that can vend WeakPtr. It’s on us, humans, to decide which objects need 
> to be ref counted or need to be CanMakeWeakPtr.
>
> Consider the following example. This code most certainly will lead to a 
> use-after-free of “parent” in the third line because the code doesn’t keep 
> the parent alive. Because isContentEditable updates style in some cases, it 
> could lead to arbitrary script execution which could remove the parent from 
> the document.
>
> Node* parent = element->parentElement();
> if (parent->isContentEditable())
>     parent->scrollIntoView();
>
> In general, relying on a complex data structure such as DOM tree to keep 
> RefCounted objects alive while we call a non-trivial function is not safe. 
> All it takes for the code to have a use-after-free is for someone to start 
> updating style, layout, etc… inside the function either directly or 
> indirectly. And we don’t want to make WebKit development really hard by 
> forcing anyone who modifies a function to check every caller of the function 
> and their callers, etc… to make sure it’s safe to do so.
>
> For this reason, it’s dangerous to store a raw pointer or a reference to a 
> ref counted object as a local variable and use it after calling a non-trivial 
> function. We did a similar analysis of a number of other patterns and usage 
> of ref counted objects in WebKit and came up with the following basic rules 
> for using ref counted objects in a safe manner. We’re hoping that these rules 
> would be eventually incorporated into our coding style guideline: 
> https://webkit.org/code-style-guidelines/
>
> Rules for Using Ref Counted Objects
>
> * Every data member to a ref counted object must use either Ref, RefPtr, or 
> WeakPtr. webkit.NoUncountedMemberChecker
> * Every ref counted base class, if it has a derived class, must define a 
> virtual destructor. webkit.RefCntblBaseVirtualDtor
> * Every ref counted object passed to a non-trivial function as an argument 
> (including "this" pointer) should be stored as a Ref or RefPtr in the 
> caller’s local scope unless it's an argument to the caller itself by 
> transitive property [1]. alpha.webkit.UncountedCallArgsChecker
> * Every ref counted object must be captured using Ref or RefPtr for lambda. 
> webkit.UncountedLambdaCapturesChecker
> * Local variables - we’re still working on this 
> (https://reviews.llvm.org/D83259)
>
> Below, I've dissected each one of these rules with the real warning emitted 
> by the analysis tool in development. Please let me know if you have any 
> comments / concerns.
>
> ----------------------------------------
> (1) is pretty trivial. Every ref counted data member should be stored using 
> Ref, RefPtr, or WeakPtr since it would be not trivially obvious that life 
> cycles of two or more objects are correctly tied or managed together.
>
> ----------------------------------------
> (2) is also pretty easy to understand. In the following example, if someone 
> destroys an instance of B using Ref<A>, then it would result in an undefined 
> behavior.
>
> struct A : public RefCounted<A> {
>         Vector<int> someData;
> };
>
> struct B : public A {
>         Vector<int> otherData;
> };
>
> ----------------------------------------
> For (3), passing a ref counted object as a raw pointer or reference to a 
> function as an argument, the tool may generate a warning like this:
>
> Source/WebCore/html/FormAssociatedElement.cpp:181:13: warning: [WebKit] call 
> argument is a raw pointers to a ref-countable type 
> [-Wfunction-arg-ptr-to-refcntbl]
>     setForm(findAssociatedForm(&asHTMLElement(), originalForm.get()));
>             ^
>
> This warns that void setForm(HTMLFormElement*) is called with the result of 
> findAssociatedForm, which returns HTMLFormElement* without storing it 
> anywhere. If setForm can somehow cause HTMLFormElement to be deleted, then 
> this can result in the use-after-free in setForm.
>
> void FormAssociatedElement::resetFormOwner()
> {
>     RefPtr<HTMLFormElement> originalForm = m_form.get();
>     setForm(findAssociatedForm(&asHTMLElement(), originalForm.get()));
>     HTMLElement& element = asHTMLElement();
>     auto* newForm = m_form.get();
>     if (newForm && newForm != originalForm && newForm->isConnected())
>         element.document().didAssociateFormControl(element);
> }
>
> Why, you may ask, we don't put HTMLFormElement* in a Ref or RefPtr in setForm 
> instead? This is because a lot of code has calls to a function with a local 
> variable. Because the callee doesn’t know whether the caller has already 
> stored each argument in Ref / RefPtr or not, we need to be safe and store 
> them again in Ref / RefPtr, resulting in an unnecessary ref-churn. 
> Additionally, if we took the approach of the callee being responsible for 
> keeping every argument alive, then every non-trivial member function of a ref 
> counted object must have "protectedThis" at the beginning of the function, 
> which would be particularly wasteful if those member functions are sometimes 
> called by other member functions.
>
> Once this rule is applied everywhere, for example, we can get rid of 
> “protectedThis” from our codebase because it would be redundant. Furthermore, 
> this rule transitively allows a raw pointer or a reference passed in as an 
> argument to be used as arguments to call another function without storing it 
> in Ref or RefPtr.
>
> For example, the following function satisfies this rule because both 
> oldDocument and newDocument are raw references passed to this function as 
> arguments, and therefore the caller of this function must have already stored 
> as Ref / RefPtr as local variables.
>
> void HTMLMediaElement::didMoveToNewDocument(Document& oldDocument, Document& 
> newDocument)
> {
>     ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
>     if (m_shouldDelayLoadEvent) {
>         oldDocument.decrementLoadEventDelayCount();
>         newDocument.incrementLoadEventDelayCount();
>     }
>
>     unregisterWithDocument(oldDocument);
>     registerWithDocument(newDocument);
>
>     HTMLElement::didMoveToNewDocument(oldDocument, newDocument);
>     updateShouldAutoplay();
> }
>
> ----------------------------------------
>
> For (4), capturing a ref counted object as a raw pointer or a reference, the 
> tool may generate a warning like this:
>
> Source/WebKit/UIProcess/API/APIHTTPCookieStore.cpp:115:74: warning: [WebKit] 
> captured a raw pointer to a ref-countable type 
> [-Wlambda-capture-ptr-to-refcntbl]
>     cookieManager->deleteCookie(m_owningDataStore->sessionID(), cookie, [pool 
> = WTFMove(pool), completionHandler = WTFMove(completionHandler)]() mutable {
>                                                                          ^
> This is warning about webPage object getting captured using a raw reference 
> in the following function:
>
> void HTTPCookieStore::deleteCookie(const WebCore::Cookie& cookie, 
> CompletionHandler<void()>&& completionHandler)
> {
>     auto* pool = m_owningDataStore->processPoolForCookieStorageOperations();
>     if (!pool) {
>         if (m_owningDataStore->sessionID() == 
> PAL::SessionID::defaultSessionID() && !cookie.session)
>             deleteCookieFromDefaultUIProcessCookieStore(cookie);
>         else
>             m_owningDataStore->removePendingCookie(cookie);
>
>         RunLoop::main().dispatch([completionHandler = 
> WTFMove(completionHandler)] () mutable {
>             completionHandler();
>         });
>         return;
>     }
>
>     auto* cookieManager = pool->supplement<WebKit::WebCookieManagerProxy>();
>     cookieManager->deleteCookie(m_owningDataStore->sessionID(), cookie, [pool 
> = WTFMove(pool), completionHandler = WTFMove(completionHandler)]() mutable {
>         completionHandler();
>     });
> }
>
> Here, we should have stored WebProcessPool in Ref instead.

Sounds great! A few questions:
* Do I understand correctly that analyzer is a part of upstream clang and can 
work on any platform?
* Does it require WebKit trunk or can work with older branches?
* Any plans to detect other kinds of misuses like circular references?


-- 
Regards,
Konstantin
_______________________________________________
webkit-dev mailing list
webkit-dev@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-dev

Reply via email to