I sugget you don't search any report about it to prevents get too much info like patch.
This time we do it by code audit
What is happening is that the BlinkScrollbarPartAnimation instance passed to BlinkScrollbarPartAnimationTimer is released while the BlinkScrollbarPartAnimationTimer::TimerFired method runs as part of BlinkScrollbarPartAnimation::setCurrentProgress call, during the execution of ScrollbarPainter::setKnobAlpha which ends up calling BlinkScrollbarPainterDelegate::setUpAlphaAnimation through a chain of observers. BlinkScrollbarPainterDelegate::setUpAlphaAnimation releases the BlinkScrollbarPartAnimation instance which gets deallocated. BlinkScrollbarPartAnimation::setCurrentProgress continues execution after ScrollbarPainter::setKnobAlpha returns, but the _scrollbarPointer is overwritten with garbage and when SetNeedsPaintInvalidation is called the crash happens.
You'd better read these to have a preliminary understanding of Blink and make sure you know a little about Objective-C
For more info click me! But you'd better not do this
https://bugs.chromium.org/p/chromium/issues/detail?id=1189926
Because the hash we get from issue page is related to chromium, we need download the chromium source code and blink is one part of it. I upload the main file and you can try to fetch chromium.
The hash 6c3c857b90ef63822c8e598bdb7aea604ba1688c
we can analysis the source file online or offline.
class BlinkScrollbarPartAnimationTimer {
public:
BlinkScrollbarPartAnimationTimer(
BlinkScrollbarPartAnimation* animation,
CFTimeInterval duration,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: timer_(std::move(task_runner),
this,
&BlinkScrollbarPartAnimationTimer::TimerFired),
start_time_(0.0),
duration_(duration),
animation_(animation),
timing_function_(CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {}
private:
void TimerFired(TimerBase*) {
double current_time = base::Time::Now().ToDoubleT();
double delta = current_time - start_time_;
if (delta >= duration_)
timer_.Stop();
// This is a speculative fix for crbug.com/1183276.
if (!animation_)
return;
double fraction = delta / duration_;
fraction = clampTo(fraction, 0.0, 1.0);
double progress = timing_function_->Evaluate(fraction);
[animation_ setCurrentProgress:progress];
}
TaskRunnerTimer<BlinkScrollbarPartAnimationTimer> timer_;
double start_time_; // In seconds.
double duration_; // In seconds.
BlinkScrollbarPartAnimation* animation_; // Weak, owns this.
scoped_refptr<CubicBezierTimingFunction> timing_function_;
};
- (void)setCurrentProgress:(NSAnimationProgress)progress {
DCHECK(_scrollbar);
CGFloat currentValue;
if (_startValue > _endValue)
currentValue = 1 - progress;
else
currentValue = progress;
blink::ScrollbarPart invalidParts = blink::kNoPart;
switch (_featureToAnimate) {
case ThumbAlpha:
[_scrollbarPainter setKnobAlpha:currentValue]; // call ScrollbarPainter::setKnobAlpha
break;
case TrackAlpha:
[_scrollbarPainter setTrackAlpha:currentValue];
invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart);
break;
case UIStateTransition:
[_scrollbarPainter setUiStateTransitionProgress:currentValue];
invalidParts = blink::kAllParts;
break;
case ExpansionTransition:
[_scrollbarPainter setExpansionTransitionProgress:currentValue];
invalidParts = blink::kThumbPart;
break;
}
_scrollbar->SetNeedsPaintInvalidation(invalidParts);
}
============================================================================
- (void)scrollerImp:(id)scrollerImp
animateKnobAlphaTo:(CGFloat)newKnobAlpha
duration:(NSTimeInterval)duration {
if (!_scrollbar)
return;
DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
[self setUpAlphaAnimation:_knobAlphaAnimation // call BlinkScrollbarPainterDelegate::setUpAlphaAnimation
scrollerPainter:scrollerPainter
part:blink::kThumbPart
animateAlphaTo:newKnobAlpha
duration:duration];
}
============================================================================
- (void)setUpAlphaAnimation:
(base::scoped_nsobject<BlinkScrollbarPartAnimation>&)
scrollbarPartAnimation
scrollerPainter:(ScrollbarPainter)scrollerPainter
part:(blink::ScrollbarPart)part
animateAlphaTo:(CGFloat)newAlpha
duration:(NSTimeInterval)duration {
blink::MacScrollbarAnimator* scrollbar_animator =
_scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();
DCHECK(scrollbar_animator);
// If the user has scrolled the page, then the scrollbars must be animated
// here.
// This overrides the early returns.
bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad();
if (scrollbar_animator->ScrollbarPaintTimerIsActive() && !mustAnimate)
return;
if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() &&
!mustAnimate) {
scrollbar_animator->StartScrollbarPaintTimer();
return;
}
// At this point, we are definitely going to animate now, so stop the timer.
scrollbar_animator->StopScrollbarPaintTimer();
// If we are currently animating, stop
if (scrollbarPartAnimation) {
[scrollbarPartAnimation invalidate];
scrollbarPartAnimation.reset();
}
scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc]
initWithScrollbar:_scrollbar
featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha
animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]
: [scrollerPainter trackAlpha]
animateTo:newAlpha
duration:duration
taskRunner:_taskRunner]);
[scrollbarPartAnimation startAnimation];
}
Do this exercise by yourself, If you find my answer have something wrong, please correct it.
My answer
We can start from TimerFired
func
class BlinkScrollbarPartAnimationTimer [ ... ]
void TimerFired(TimerBase*) {
double current_time = base::Time::Now().ToDoubleT();
double delta = current_time - start_time_;
if (delta >= duration_)
timer_.Stop();
// This is a speculative fix for crbug.com/1183276.
if (!animation_)
return;
double fraction = delta / duration_;
fraction = clampTo(fraction, 0.0, 1.0);
double progress = timing_function_->Evaluate(fraction);
[animation_ setCurrentProgress:progress]; [1] call setCurrentProgress. Notice `animation_`
}
private:
BlinkScrollbarPartAnimation* animation_; [2] weak, own this
[2] BlinkScrollbarPartAnimationTimer
own the instance of BlinkScrollbarPartAnimation
which assignmented by constructor. This make a chance to trigger uaf.
- (void)setCurrentProgress:(NSAnimationProgress)progress {
DCHECK(_scrollbar);
CGFloat currentValue;
blink::ScrollbarPart invalidParts = blink::kNoPart;
switch (_featureToAnimate) {
case ThumbAlpha:
[_scrollbarPainter setKnobAlpha:currentValue]; [3] call ScrollbarPainter::setKnobAlpha
break;
case TrackAlpha:
[_scrollbarPainter setTrackAlpha:currentValue];
invalidParts = static_cast<blink::ScrollbarPart>(~blink::kThumbPart);
break;
}
_scrollbar->SetNeedsPaintInvalidation(invalidParts);
}
setCurrentProgress
can call setKnobAlpha
- (void)scrollerImp:(id)scrollerImp
animateKnobAlphaTo:(CGFloat)newKnobAlpha
duration:(NSTimeInterval)duration {
if (!_scrollbar)
return;
DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));
ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
[self setUpAlphaAnimation:_knobAlphaAnimation [4] call BlinkScrollbarPainterDelegate::setUpAlphaAnimation
scrollerPainter:scrollerPainter
part:blink::kThumbPart
animateAlphaTo:newKnobAlpha
duration:duration];
}
- (void)setUpAlphaAnimation:
(base::scoped_nsobject<BlinkScrollbarPartAnimation>&)
scrollbarPartAnimation
scrollerPainter:(ScrollbarPainter)scrollerPainter
part:(blink::ScrollbarPart)part
animateAlphaTo:(CGFloat)newAlpha
duration:(NSTimeInterval)duration {
blink::MacScrollbarAnimator* scrollbar_animator =
_scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();
DCHECK(scrollbar_animator);
// If we are currently animating, stop
if (scrollbarPartAnimation) { [5]
[scrollbarPartAnimation invalidate];
scrollbarPartAnimation.reset();
}
scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc] [6]
initWithScrollbar:_scrollbar
featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha
animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]
: [scrollerPainter trackAlpha]
animateTo:newAlpha
duration:duration
taskRunner:_taskRunner]);
[scrollbarPartAnimation startAnimation];
}
The (base::scoped_nsobject<BlinkScrollbarPartAnimation>&) scrollbarPartAnimation
can be release by reset()
-
About
scoped_nsobject
:scoped_nsobject<>
is patterned after std::unique_ptr<>, but maintains ownership of an NSObject subclass object. Style deviations here are solely for compatibility with std::unique_ptr<>'s interface, with which everyone is already familiar.scoped_nsobject<> takes ownership of an object (in the constructor or in reset()) by taking over the caller's existing ownership claim. The caller must own the object it gives to scoped_nsobject<>, and relinquishes an ownership claim to that object. scoped_nsobject<> does not call -retain, callers have to call this manually if appropriate.
-
About
void base::scoped_nsprotocol< NST >::reset( NST object = nil )
:// We intentionally do not check that object != object_ as the caller must // either already have an ownership claim over whatever it passes to this // method, or call it with the |RETAIN| policy which will have ensured that // the object is retained once more when reaching this point. [object_ release]; object_ = object; }