Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Blank page on location change / reload if a filesystem asynchronous call was pending #33164

Closed
3 tasks done
remss opened this issue Mar 7, 2022 · 17 comments · Fixed by #33252 or #33280
Closed
3 tasks done
Assignees
Labels
17-x-y bug 🪲 has-repro-gist Issue can be reproduced with code at https://gist.github.com/ platform/all status/confirmed A maintainer reproduced the bug or agreed with the feature

Comments

@remss
Copy link

remss commented Mar 7, 2022

Preflight Checklist

Electron Version

17.1.1

What operating system are you using?

Windows

Operating System Version

Windows 10 21H2

What arch are you using?

x64

Last Known Working Electron version

13.6.9

Expected Behavior

Assuming a BrowserWindow like this:

const mainWindow = new BrowserWindow({
    ...
    webPreferences: {
        nodeIntegration: true,
        contextIsolation: false,
    }
})

And an asynchronous filesystem call on fs.promises API

// renderer.js
const fs = require('fs');
async function asyncStuff() {
    let output = await fs.promises.readFile("datafile");
}

Then trying to reload the page when the fs asynchronous call is not finished yet will make the page fail (blank page).

// renderer.js
async function testCrash() {
    asyncStuff() // No await here
    window.location.reload() // Here the renderer will crash
})

I expect that the page reloads correctly.

Actual Behavior

The page is blank, and DevTools are disconnected.

Testcase Gist URL

No response

Additional Information

Notes:

  • Replace the fs.promises.readFile() by fs.readFileSync() and the page will reload fine
  • Replace the fs.promises.readFile() by fs.readFile() with callback and the page will reload fine
  • The bug is here also with a window.location.href
  • I tried with other async api and the page works fine (like https.get(), timers.promises.setTimeout(), or fetch())

I created a repo to reproduce the bug: https://github.com/remss/electron-issue-reload
Just run npm i && npm start then click on reloadMeWithAsyncStuff button to see the blank page.

@remss remss added the bug 🪲 label Mar 7, 2022
@codebytere codebytere self-assigned this Mar 8, 2022
@DanielMcAssey
Copy link

Getting this too

@codebytere
Copy link
Member

codebytere commented Mar 9, 2022

@codebytere codebytere added 17-x-y has-repro-gist Issue can be reproduced with code at https://gist.github.com/ status/confirmed A maintainer reproduced the bug or agreed with the feature labels Mar 9, 2022
@DanielMcAssey
Copy link

Just to add I cant replicate this on 16.1.0

@codebytere
Copy link
Member

@DanielMcAssey correct, it's from a version of V8 introduced in 17.

@deepak1556
Copy link
Member

@jkleinsc the crash looks unrelated to cppgc and #33252 does not fix this one, reopening for further investigation.

Crash reason:  EXCEPTION_ACCESS_VIOLATION_READ
Crash address: 0xffffffffffffffff
Process uptime: 3 seconds

Thread 0 (crashed)
 0  electron.exe!std::__1::__hash_table<std::__1::__hash_value_type<node::FastStringKey,node::BaseObjectPtrImpl<node::BaseObject,0> >,std::__1::__unordered_map_hasher<node::FastStringKey,std::__1::__hash_value_type<node::FastStringKey,node::BaseObjectPtrImpl<node::BaseObject,0> >,node::FastStringKey::Hash,std::__1::equal_to<node::FastStringKey>,1>,std::__1::__unordered_map_equal<node::FastStringKey,std::__1::__hash_value_type<node::FastStringKey,node::BaseObjectPtrImpl<node::BaseObject,0> >,std::__1::equal_to<node::FastStringKey>,node::FastStringKey::Hash,1>,std::__1::allocator<std::__1::__hash_value_type<node::FastStringKey,node::BaseObjectPtrImpl<node::BaseObject,0> > > >::find<node::FastStringKey>(node::FastStringKey const &) [__hash_table : 2394 + 0x0]
    rax = 0xcdcdcdcdcdcdcdcd   rdx = 0x2555555555555555
    rcx = 0x000012fe00a8c9b0   rbx = 0x0101010101010101
    rsi = 0x000000faad7fc200   rdi = 0x0000000000000028
    rbp = 0x0000000000000000   rsp = 0x000000faad7fba50
     r8 = 0x00007ff7a5081220    r9 = 0x7b60c3798090b241
    r10 = 0x000000faad7fbaa8   r11 = 0x7b60c3798090b241
    r12 = 0xffffffffffffffff   r13 = 0x000012fe01284000
    r14 = 0x0000000000000006   r15 = 0xcdcdcdcdcdcdcdcd
    rip = 0x00007ff7a0bd5c3c
    Found by: given as instruction pointer in context
 1  electron.exe!node::fs::GetReqWrap(v8::FunctionCallbackInfo<v8::Value> const &,int,bool) [node_file-inl.h : 230 + 0x3f]
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fba80   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x00007ff7a0bfb592
    Found by: call frame info
 2  electron.exe!static void node::fs::Read(const class v8::FunctionCallbackInfo<v8::Value> & const) [node_file.cc : 2067 + 0x10]
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fbae0   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x00007ff7a0bfece8
    Found by: call frame info
 3  electron.exe!v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) [api-arguments-inl.h : 152 + 0x9]
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fbde0   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x00007ff79cbdc6b5
    Found by: call frame info
 4  electron.exe!static class v8::internal::MaybeHandle<v8::internal::Object> v8::internal::`anonymous namespace'::HandleApiCallHelper<0>(class v8::internal::Isolate *, class v8::internal::Handle<v8::internal::HeapObject>, class v8::internal::Handle<v8::internal::HeapObject>, class v8::internal::Handle<v8::internal::FunctionTemplateInfo>, class v8::internal::Handle<v8::internal::Object>, class v8::internal::BuiltinArguments) [builtins-api.cc : 112 + 0xb]
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fbee0   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x00007ff79cbda7a7
    Found by: call frame info
 5  electron.exe!static class v8::internal::Object v8::internal::Builtin_Impl_HandleApiCall(class v8::internal::BuiltinArguments, class v8::internal::Isolate *) [builtins-api.cc : 142 + 0x3a]
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fc000   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x00007ff79cbd8859
    Found by: call frame info
 6  electron.exe!v8::internal::Builtin_HandleApiCall(int,unsigned __int64 *,v8::internal::Isolate *) [builtins-api.cc : 130 + 0x2d]
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fc0f0   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x00007ff79cbd81be
    Found by: call frame info
 7  0x45cb07ededfc
    rbx = 0x0101010101010101   rbp = 0x0000000000000000
    rsp = 0x000000faad7fc160   r12 = 0xffffffffffffffff
    r13 = 0x000012fe01284000   r14 = 0x0000000000000006
    r15 = 0xcdcdcdcdcdcdcdcd   rip = 0x000045cb07ededfc
    Found by: call frame info

@deepak1556 deepak1556 reopened this Mar 14, 2022
@codebytere
Copy link
Member

@deepak1556 sounds good, i'll dig into it.

@codebytere
Copy link
Member

codebytere commented Mar 14, 2022

@DanielMcAssey I can repro it starting with 16.0.5 using my above gist.

Looks like it was caused by 73ba0cb cc @zcbenz

@remss
Copy link
Author

remss commented Mar 15, 2022

Using my repro case, here the result with different versions of Electron:

  • v13.6.9 : PASS, I cannot reproduce the issue
  • v14.2.7 : FAIL, I reproduce the issue
  • v15.4.1 : FAIL, I reproduce the issue
  • v16.1.0 : FAIL, I reproduce the issue
  • v17.1.1 : FAIL, I reproduce the issue

@deepak1556
Copy link
Member

@codebytere to further narrow down the trigger, most of the changes in that PR are guarded behind window opener checks, I think the explicit call to run microtasks

gin_helper::MicrotasksScope microtasks_scope(env->isolate());
before destroying the node environment might be the most likely culprit here since the UAF is from the fs promises api.

@deepak1556
Copy link
Member

node::FreeEnvironment disabled JS execution https://github.com/nodejs/node/blob/0367b5c35ea0f98b323175a4aaa8e651af7a91e7/src/api/environment.cc#L368-L369 , so not sure if we want to run the microtasks at this point.

@codebytere
Copy link
Member

codebytere commented Mar 15, 2022

@deepak1556 yep sorry should have updated this - that's also what I found and have been working to fix! Also not sure if we want to run microtasks, or rather force flush the pending tasks. If we don't run them however it DCHECKs:

#
# Fatal error in ../../v8/src/api/api-inl.h, line 185
# Debug check failed: microtask_queue->GetMicrotasksScopeDepth() || !microtask_queue->DebugMicrotasksScopeDepthIsZero().
#
#
#
#FailureMessage Object: 0x7ff7bbf64fe0
0   Electron Framework                  0x000000012a4645b9 base::debug::CollectStackTrace(void**, unsigned long) + 9
1   Electron Framework                  0x000000012a387313 base::debug::StackTrace::StackTrace() + 19
2   Electron Framework                  0x000000012c2eafbd gin::(anonymous namespace)::PrintStackTrace() + 45
3   Electron Framework                  0x000000012bd38296 V8_Fatal(char const*, int, char const*, ...) + 326
4   Electron Framework                  0x000000012bd37d35 v8::base::(anonymous namespace)::DefaultDcheckHandler(char const*, int, char const*) + 21
5   Electron Framework                  0x00000001275acf90 v8::CallDepthScope<true>::~CallDepthScope() + 1040
6   Electron Framework                  0x000000012757ed26 v8::Promise::Resolver::Resolve(v8::Local<v8::Context>, v8::Local<v8::Value>) + 566
7   Electron Framework                  0x000000012f4c0a3c node::fs::FSReqPromise<node::AliasedBufferBase<double, v8::Float64Array, void> >::Resolve(v8::Local<v8::Value>) + 220
8   Electron Framework                  0x000000012f4cc84b node::fs::AfterInteger(uv_fs_s*) + 187

reverting to that would fix the crash, but i'd prefer a more proper fix 🤔

@deepak1556
Copy link
Member

During the deletion of script context, we make sure to stop running node callbacks

// When doing navigation without restarting renderer process, it may happen
// that the node environment is destroyed but the message loop is still there.
// In this case we should not run uv loop.
if (!env)
return;
if the main frame is getting disposed
// The main frame may be replaced.
if (env == node_bindings_->uv_env())
node_bindings_->set_uv_env(nullptr);
, I think the above promise tasks are still run during environment deletion probably because of this call https://github.com/nodejs/node/blob/a01302b8dfaa9aa6290cd6bee552c4ce30dca34c/src/api/environment.cc#L384-L386. Can you check if the DCHECK triggers when the drain tasks call is commented out ? I am wondering if the drain tasks should be called before RunCleanup which basically will delete all open handles and api wrappers from node.

@remss
Copy link
Author

remss commented Mar 18, 2022

@codebytere The bug persists using electron@18.0.0-beta.4
I updated my repro case https://github.com/remss/electron-issue-reload/tree/electron-reload-issue

@deepak1556
Copy link
Member

@remss the fix for this issue has not been merged into the 18-x-y branch yet, you can track it at #33302

@remss
Copy link
Author

remss commented Mar 18, 2022

Ho sorry, I was confused by the release note of https://github.com/electron/electron/releases/tag/v18.0.0-beta.4 and random crash in renderer.

@liangwenzhong
Copy link

any solution ?

@DanielMcAssey
Copy link

This was fixed a minor version or 2 ago

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
17-x-y bug 🪲 has-repro-gist Issue can be reproduced with code at https://gist.github.com/ platform/all status/confirmed A maintainer reproduced the bug or agreed with the feature
Projects
None yet
5 participants