Skip to content

fix: refactor to avoid UAF in NodeStreamLoader - #52474

Merged
dsanders11 merged 3 commits into
mainfrom
fix/uaf-node-stream-loader
Jul 29, 2026
Merged

fix: refactor to avoid UAF in NodeStreamLoader#52474
dsanders11 merged 3 commits into
mainfrom
fix/uaf-node-stream-loader

Conversation

@dsanders11

@dsanders11 dsanders11 commented Jul 27, 2026

Copy link
Copy Markdown
Member

Description of Change

Under specific circumstances NodeStreamLoader can hit a UAF when it deletes itself before completing NodeStreamLoader::NotifyReadable, so refactor to avoid that possibility.

Test added as a separate commit first to show the ASan failure flagging the UAF: https://github.com/electron/electron/actions/runs/30314440929/job/90138979629

Also added tests for two other edge cases which get fixed by the refactor, which would cause the streaming response to stall out: https://github.com/electron/electron/actions/runs/30321732441/job/90160483107

Checklist

Release Notes

Notes: Fixed a UAF with protocol.registerStreamProtocol when an error is emitted during a read

@electron-cation electron-cation Bot added the new-pr 🌱 PR opened recently label Jul 27, 2026
@dsanders11 dsanders11 added semver/patch backwards-compatible bug fixes target/41-x-y PR should also be added to the "41-x-y" branch. target/42-x-y PR should also be added to the "42-x-y" branch. target/43-x-y PR should also be added to the "43-x-y" branch. target/44-x-y PR should also be added to the "44-x-y" branch. labels Jul 28, 2026
@dsanders11
dsanders11 marked this pull request as ready for review July 28, 2026 05:28

@deepak1556 deepak1556 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was this case identified ?

@dsanders11

Copy link
Copy Markdown
Member Author

@deepak1556, it was flagged by the Clang Static Analyzer, via clang-tidy. I'm looking to enable these checks in our .clang-tidy file and working through the reports locally.

clang-tidy output
../../electron/shell/browser/net/node_stream_loader.cc:86:13: warning: Use of memory after it is released [clang-analyzer-cplusplus.NewDelete]
   86 |   readable_ = true;
      |   ~~~~~~~~~ ^
../../electron/shell/browser/net/node_stream_loader.cc:82:7: note: Assuming field 'readable_' is false
   82 |   if (!readable_)
      |       ^~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:82:3: note: Taking true branch
   82 |   if (!readable_)
      |   ^
../../electron/shell/browser/net/node_stream_loader.cc:83:5: note: Calling 'NodeStreamLoader::ReadMore'
   83 |     ReadMore();
      |     ^~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:105:7: note: Assuming field 'is_reading_' is false
  105 |   if (is_reading_) {
      |       ^~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:105:3: note: Taking false branch
  105 |   if (is_reading_) {
      |   ^
../../electron/shell/browser/net/node_stream_loader.cc:117:3: note: Control jumps to 'case 0:'  at line 117
  117 |   DCHECK(weak) << "We shouldn't have been destroyed when calling read()";
      |   ^
../../base/check.h:293:3: note: expanded from macro 'DCHECK'
  293 |   LOGGING_CHECK_FUNCTION_IMPL(::logging::CheckError::DCheck(#condition), \
      |   ^
../../base/check.h:225:3: note: expanded from macro 'LOGGING_CHECK_FUNCTION_IMPL'
  225 |   switch (0)                                                 \
      |   ^
../../electron/shell/browser/net/node_stream_loader.cc:117:3: note: '?' condition is true
  117 |   DCHECK(weak) << "We shouldn't have been destroyed when calling read()";
      |   ^
../../base/check.h:293:3: note: expanded from macro 'DCHECK'
  293 |   LOGGING_CHECK_FUNCTION_IMPL(::logging::CheckError::DCheck(#condition), \
      |   ^
../../base/check.h:228:30: note: expanded from macro 'LOGGING_CHECK_FUNCTION_IMPL'
  228 |     if (ANALYZER_ASSUME_TRUE((condition) ? true : false))    \
      |                              ^
../../electron/shell/browser/net/node_stream_loader.cc:117:3: note: Taking true branch
  117 |   DCHECK(weak) << "We shouldn't have been destroyed when calling read()";
      |   ^
../../base/check.h:293:3: note: expanded from macro 'DCHECK'
  293 |   LOGGING_CHECK_FUNCTION_IMPL(::logging::CheckError::DCheck(#condition), \
      |   ^
../../base/check.h:228:5: note: expanded from macro 'LOGGING_CHECK_FUNCTION_IMPL'
  228 |     if (ANALYZER_ASSUME_TRUE((condition) ? true : false))    \
      |     ^
../../electron/shell/browser/net/node_stream_loader.cc:121:29: note: Left side of '||' is true
  121 |   if (!ret.ToLocal(&buffer) || !node::Buffer::HasInstance(buffer)) {
      |                             ^
../../electron/shell/browser/net/node_stream_loader.cc:125:9: note: Assuming field 'has_read_waiting_' is false
  125 |     if (has_read_waiting_) {
      |         ^~~~~~~~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:125:5: note: Taking false branch
  125 |     if (has_read_waiting_) {
      |     ^
../../electron/shell/browser/net/node_stream_loader.cc:132:9: note: Assuming field 'pending_result_' is true
  132 |     if (pending_result_) {
      |         ^~~~~~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:132:5: note: Taking true branch
  132 |     if (pending_result_) {
      |     ^
../../electron/shell/browser/net/node_stream_loader.cc:133:7: note: Calling 'NodeStreamLoader::NotifyComplete'
  133 |       NotifyComplete(result_);
      |       ^~~~~~~~~~~~~~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:91:7: note: Field 'is_reading_' is false
   91 |   if (is_reading_ || is_writing_) {
      |       ^
../../electron/shell/browser/net/node_stream_loader.cc:91:7: note: Left side of '||' is false
../../electron/shell/browser/net/node_stream_loader.cc:91:22: note: Assuming field 'is_writing_' is false
   91 |   if (is_reading_ || is_writing_) {
      |                      ^~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:91:3: note: Taking false branch
   91 |   if (is_reading_ || is_writing_) {
      |   ^
../../electron/shell/browser/net/node_stream_loader.cc:101:3: note: Memory is released
  101 |   delete this;
      |   ^~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:133:7: note: Returning; memory was released
  133 |       NotifyComplete(result_);
      |       ^~~~~~~~~~~~~~~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:83:5: note: Returning; memory was released
   83 |     ReadMore();
      |     ^~~~~~~~~~
../../electron/shell/browser/net/node_stream_loader.cc:86:13: note: Use of memory after it is released
   86 |   readable_ = true;
      |   ~~~~~~~~~ ^

@deepak1556

Copy link
Copy Markdown
Member

Awesome, thank you!

@dsanders11
dsanders11 merged commit ad5df71 into main Jul 29, 2026
90 checks passed
@release-clerk

release-clerk Bot commented Jul 29, 2026

Copy link
Copy Markdown

Release Notes Persisted

Fixed a UAF with protocol.registerStreamProtocol when an error is emitted during a read

@trop

trop Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

I have automatically backported this PR to "41-x-y", please check out #52513

@trop

trop Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

I have automatically backported this PR to "43-x-y", please check out #52514

@trop trop Bot added in-flight/43-x-y and removed target/41-x-y PR should also be added to the "41-x-y" branch. labels Jul 29, 2026
@trop

trop Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

I have automatically backported this PR to "44-x-y", please check out #52515

@trop trop Bot removed the target/43-x-y PR should also be added to the "43-x-y" branch. label Jul 29, 2026
@trop

trop Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

I have automatically backported this PR to "42-x-y", please check out #52516

@trop trop Bot added in-flight/44-x-y in-flight/42-x-y merged/44-x-y PR was merged to the "44-x-y" branch. merged/43-x-y PR was merged to the "43-x-y" branch. merged/42-x-y PR was merged to the "42-x-y" branch. merged/41-x-y PR was merged to the "41-x-y" branch. and removed target/44-x-y PR should also be added to the "44-x-y" branch. target/42-x-y PR should also be added to the "42-x-y" branch. in-flight/44-x-y in-flight/43-x-y in-flight/42-x-y in-flight/41-x-y labels Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

45-x-y merged/41-x-y PR was merged to the "41-x-y" branch. merged/42-x-y PR was merged to the "42-x-y" branch. merged/43-x-y PR was merged to the "43-x-y" branch. merged/44-x-y PR was merged to the "44-x-y" branch. new-pr 🌱 PR opened recently semver/patch backwards-compatible bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants