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

Internal node assertion caused by js copy mechanism #55302

Open
artur-ma opened this issue Oct 7, 2024 · 21 comments
Open

Internal node assertion caused by js copy mechanism #55302

artur-ma opened this issue Oct 7, 2024 · 21 comments
Labels
net Issues and PRs related to the net subsystem.

Comments

@artur-ma
Copy link

artur-ma commented Oct 7, 2024

Version

22.9.0

Platform

image node:22.9.0-bullseye-slim

Subsystem

No response

What steps will reproduce the bug?

Run in node cli:

const undici = require('undici')
const { default: fastCopy } = require('fast-copy')
const a = new undici.Agent()
a.request({ method: 'GET', origin: 'https://google.com', path: '/' }).then(r => r.body.text())
fastCopy(a)

How often does it reproduce? Is there a required condition?

everytime

What is the expected behavior? Why is that the expected behavior?

Have meaningful protection and error message on JS level

What do you see instead?

Process crash

│  node[80]: static void node::TCPWrap::New(const v8::FunctionCallbackInfo<v8::Value>&) at ../src/tcp_wrap.cc:155  
│   #  Assertion failed: args[0]->IsInt32()                                                                                                                                                                                                   
│                                                                                                                                                                                                                                             │
│ ----- Native stack trace -----                                                                                                                                                                                                                                                                                                                                                                                                                                                       
│  1: 0xf462ec node::Assert(node::AssertionInfo const&) [node]                                                                                                                                                                                
│  2: 0x1088d7c node::TCPWrap::New(v8::FunctionCallbackInfo<v8::Value> const&) [node]                                                                                                                                                         
│  3: 0x1239b24  [node]                                                                                                                                                                                                                       
│  4: 0x1239dcc v8::internal::Builtin_HandleApiConstruct(int, unsigned long*, v8::internal::Isolate*) [node]                                                                                                                                  
│  5: 0x1cfb8f4  [node]                                                                                                                                                                                                                                                                                                                                                                                                                                                                 │
│ ----- JavaScript stack trace -----                                                                                                                                                                                                                                                                                                                                                                                                                                                  
│ 1: getCleanClone (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:52:20)                                                                                                                                                                
│ 2: copyObjectLooseModern (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:214:17)                                                                                                                                                       
│ 3: copier (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:371:20)                                                                                                                                                                      
│ 4: copyObjectLooseModern (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:226:35)                                                                                                                                                       
│ 5: copier (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:371:20)                                                                                                                                                                      
│ 6: copyObjectLooseModern (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:226:35)                                                                                                                                                       
│ 7: copier (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:371:20)                                                                                                                                                                      
│ 8: copyArrayLoose (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:147:30)                                                                                                                                                              
│ 9: copier (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:367:20)                                                                                                                                                                      
│ 10: copyObjectLooseModern (/home/app/node_modules/fast-copy/dist/cjs/index.cjs:226:35)     

Additional information

Using pure js library, without native code manipulation causes internal nodejs error and process crash

here is the library
https://github.com/planttheidea/fast-copy

@artur-ma artur-ma changed the title Internal node assertion error in native code Internal node assertion caused by js copy mechanism Oct 7, 2024
@RedYetiDev
Copy link
Member

RedYetiDev commented Oct 7, 2024

Can you reproduce without the use of this external library? If not, can you report the issue to them and find a minimal reproduction

Can you reproduce without Undici?

@RedYetiDev
Copy link
Member

CC @nodejs/undici

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

@RedYetiDev The issues is here because pure js code can cause total process crash, the library is using different techniques to copy objects, but it should not cause process crash if they are not touching internal code (C++), and they are not using process.binding

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

This issue reported multiple times, but there was always an assumption that some dependency is manipulating internals

#46093
#18389

Which is not true, this bug can occure even without touching internals

@RedYetiDev
Copy link
Member

RedYetiDev commented Oct 7, 2024

I can't reproduce the issue at all...:

const undici = require('undici')
const { default: fastCopy } = require('fast-copy')
const a = new undici.Agent()
a.request({ method: 'GET', origin: 'https://google.com', path: '/' }).then(r => r.body.text())
fastCopy(a)

@RedYetiDev RedYetiDev closed this as not planned Won't fix, can't repro, duplicate, stale Oct 7, 2024
@RedYetiDev
Copy link
Member

Because this can't be reproduced, and there's no minimal reproduction, I've closed it. I'll reopen if you give more info.

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

@RedYetiDev wdym cant be reproduced? I gave exact reproduction steps..

undici is part of nodejs project, the fast-copy is copying the object

@RedYetiDev
Copy link
Member

I can't reproduce with the steps you gave. Try reporting the issue to Undici, they may be able to help better.

Undici has some CPP bindings, so it might be an Undici issue?

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

@RedYetiDev

const net = require('net')


const socket = net.Socket()
socket.connect('google.com', 443)

for (const s of Object.getOwnPropertySymbols(socket)) {
  if (socket[s]?.constructor) {
    socket[s]?.constructor()
  }
}

Here, no dependencies...

@RedYetiDev RedYetiDev reopened this Oct 7, 2024
@RedYetiDev RedYetiDev added confirmed-bug Issues with confirmed bugs. net Issues and PRs related to the net subsystem. labels Oct 7, 2024
@RedYetiDev
Copy link
Member

RedYetiDev commented Oct 7, 2024

I'm able to reproduce now, thank you :-):

Repro A

const net = require('net')

const socket = net.Socket();
socket.connect('google.com', 443);
const kHandle = Object.getOwnPropertySymbols(socket).find((s) => s.description === 'kHandle');
new socket[kHandle].constructor();
  #  node[90025]: static void node::PipeWrap::New(const FunctionCallbackInfo<Value> &) at ../src/pipe_wrap.cc:123
  #  Assertion failed: args[0]->IsInt32()

Repro B

const net = require('net')

const socket = net.Socket();
socket.connect('google.com', 443);
const kHandle = Object.getOwnPropertySymbols(socket).find((s) => s.description === 'kHandle');
socket[kHandle].constructor();
#  node[90067]: static void node::PipeWrap::New(const FunctionCallbackInfo<Value> &) at ../src/pipe_wrap.cc:122
#  Assertion failed: args.IsConstructCall()

@targos targos removed the confirmed-bug Issues with confirmed bugs. label Oct 7, 2024
@targos
Copy link
Member

targos commented Oct 7, 2024

This is not a bug, but incorrect use of Node.js internals.

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

@targos As I mentioned before, I would expect it not to crash the whole process but to have meaningfull error and normal stack trace (if possible)

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

When I go over an object recursievly which by coincidence has socket instace reference, I do not completley agree this is "incorrect use of Node.js internals"

This is what "fast-copy" doing actually

@ronag
Copy link
Member

ronag commented Oct 7, 2024

I think when you are accessing node internals it's undefined behavior. There is no way we can provide meaningful behavior when things are accessed in a way that is not intended. If anything I would argue that the fix here is that the handle should not be available at all.

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

let me ask such question, pino-pretty uses same lib (fast-copy)
https://github.com/pinojs/pino-pretty/blob/ba1e8448f1364f7d14e7d88c9a97af48de7128dd/lib/utils/filter-log.js#L30

if i will do

const pino = require('pino')
const pretty = require('pino-pretty')
const logger = pino(pretty())
const fastify = require('fastify')

const server = fastify()

server.get(async (request) => {
  logger.info(request)
})

Do u expect the whole nodejs process to crash because request object has reference to Socket? (in this case it will not happen, because by luck pino-pretty gets already strigified data)

@RedYetiDev
Copy link
Member

Well, the assertion (AFAIK) can't be caught because it's thrown from C++land, as the Pipe (the class that causes the assertion) is from PipeWrap, a native binding.

@ronag
Copy link
Member

ronag commented Oct 7, 2024

I would expect any library that uses fast-copy to randomly crash. @mcollina wdyt?

@ronag
Copy link
Member

ronag commented Oct 7, 2024

@targos: If anything I could agree that we should move from using private symbols to using private properties in order to avoid these kinds of undefined behaviors. It's not totally unreasonable to expect that a javascript program has only defined behaviors. @aduh95 maybe also has an opinion?

@cjihrig
Copy link
Contributor

cjihrig commented Oct 7, 2024

we should move from using private symbols to using private properties in order to avoid these kinds of undefined behavior

+1. I believe that would fix this issue as well:

'use strict';
const { AsyncLocalStorage } = require('node:async_hooks');
const { deepEqual } = require('node:assert/strict');
const asyncLocalStorage = new AsyncLocalStorage();

asyncLocalStorage.run({}, () => {
  deepEqual(Promise.resolve('foo'), Promise.resolve('foo'));
});

@artur-ma
Copy link
Author

artur-ma commented Oct 7, 2024

Well, the assertion (AFAIK) can't be caught because it's thrown from C++land, as the Pipe (the class that causes the assertion) is from PipeWrap, a native binding.

Yes I understand that, my "proposal" if its possible, to "redeclare" the constructor if possible on JS side
because the constructors both of TCP class and Pipe class are called from native code as far as I understand, no one calling it from JS code, so maybe we can redeclare it on JS side that will throw or something like that?

If anything I could agree that we should move from using private symbols to using private properties in order to avoid these kinds of undefined behavior.

IMO That would be even better I guess, since as u can see in my repro code, only public Nodejs API were used to cause it.

const net = require('net')
const socket = net.Socket()
socket.connect('google.com', 443)

for (const s of Object.getOwnPropertySymbols(socket)) {
  if (socket[s]?.constructor) {
    socket[s]?.constructor()
  }
}

@mcollina
Copy link
Member

I would expect any library that uses fast-copy to randomly crash. @mcollina wdyt?

Not really, it works very well for the supported use case pino-pretty - I'm fairly unsure how they where able to get that crash. There is no supported way in pino to get there.


In other terms, most native objects in Node would do very bad things if cloned. Don't do it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
net Issues and PRs related to the net subsystem.
Projects
None yet
Development

No branches or pull requests

6 participants