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

chunked piping to stdin causes node to crash #41151

Closed
alexlamsl opened this issue Dec 12, 2021 · 8 comments
Closed

chunked piping to stdin causes node to crash #41151

alexlamsl opened this issue Dec 12, 2021 · 8 comments

Comments

@alexlamsl
Copy link

Version

v10.24.1 − v17.2.0

Platform

tested so far on macOS & Windows

Subsystem

No response

What steps will reproduce the bug?

test.js

var _calls_ = 10, a = 100, b = 10, c = 0;

function f0(foo, b_2) {
    --b + (b_2 += (c = c + 1) + ~((foo && (foo[--b + (foo && typeof foo.set == "function" && --_calls_ >= 0 && foo.set())] = (3 > 2 || 22) ^ Infinity + 0)) / (([] & [ , 0 ][1]) << (38..toString() == -2))));
    {
        var brake2 = 5;
        while (0 in {
            null: (c = c + 1) + [ --b + ([] in {}), ..."" + b_2, foo, a++ + delete (foo = -true !== this > -4 & (b_2 && (b_2[c = 1 + c,
            false * this >>> (5 > 3) == ([ , 0 ].length === 2 != -0 != + -1)] = 38..toString() | this)) < (-5 ^ -0)) ].Infinity
        } && --brake2 > 0) {
            var brake3 = 5;
            L25048: do {
                {
                    var expr4 = (c = c + 1) + (22 in [ ((c = c + 1) + (typeof await_1 != "function") || a || 3).toString() ]);
                    for (foo in expr4) {
                        L25049: for (var brake5 = 5; {
                            get: 25 in []
                        }.value && brake5 > 0; --brake5) {
                            var brake6 = 5;
                            L25050: do {
                                c = c + 1;
                            } while (a++ + void setImmediate(() => typeof f1 == "function" && --_calls_ >= 0 && f1("bar", typeof f2 == "function" && --_calls_ >= 0 && f2(1, -1, (c = 1 + c,
                            (foo && (foo[a++ + {
                                1.5: (c = 1 + c, ("object" < -0 != (22 !== 25)) < (/[a2][^e]+$/ !== "function") * ("c" >> 22))
                            }[c = 1 + c, (b_2 && (b_2[--b + (foo += (c = 1 + c, (undefined ^ 24..toString()) - (4 ^ "a") <= ("a" & "a") >>> ("a" >> /[a2][^e]+$/)))] = Infinity / "c" + (-4 || 23..toString()))) < (24..toString() >>> undefined ^ (3 ^ false))]] |= (38..toString() || -0) << (Infinity | []))) * (c = c + 1,
                            "bar" - false))))) && --brake6 > 0);
                        }
                    }
                }
            } while (3 && --brake3 > 0);
        }
    }
}

var a_2 = f0([ --a, a && typeof a.static == "function" && --_calls_ >= 0 && a.static`${(c = c + 1) + [ (a && typeof a.async == "function" && --_calls_ >= 0 && (a,
a.async)(-5, (c = 1 + c, (a && ({
    NaN: a[c = 1 + c, ("a" & this || 5 / true) & (a += ([ , 0 ][1] & "bar") > (3 > 2) - 2)]
} = {
    NaN: 24..toString() * this
})) > (-1 !== Infinity) ^ (this >>> {} | false >>> 4))) || 8).toString()[--b + ++a], a && a[--b ? b = a : --b + {
    length: (c = 1 + c, (NaN < -5) + ({} && false) == -/[a2][^e]+$/ - ("" - /[a2][^e]+$/))
}[c = 1 + c, (a = (22, true)) << ("a" >>> 38..toString()) == (4 << this) % ("" === [])]], ..."" + a, {
    undefined: (c = c + 1) + {
        set: (c = 1 + c, (delete 23..toString() > (0 & true)) / (a && (a[--b + a--] += (void {},
        null / 1)))),
    },
    in: a += (c = 1 + c, (c = c + 1, Infinity ^ -5) % (("function" ^ -1) - (NaN << "function"))),
    set: b = a
} ]}${(c = c + 1) + (a && a[--b + (a && a.length)])}${(c = c + 1) + /[abc4]/g.exec(([ , 0 ].length === 2 || b || 5).toString())}` ].__proto__, --b + ([ , a ] = []), this);

console.log(null, a, b, c, Infinity, NaN, undefined);
$ node bin/uglifyjs test.js | node
null NaN 6 3 Infinity NaN undefined
node:internal/process/task_queues:78
          callback();
          ^

TypeError: callback is not a function
    at processTicksAndRejections (node:internal/process/task_queues:78:11)

Please refer to mishoo/UglifyJS#5217 (comment) for more details.

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

Readily reproducible, but note that if you simply do cat test.js | node it would not fail.

What is the expected behavior?

No exceptions being thrown.

What do you see instead?

An exception is thrown.

Additional information

No response

@kzc
Copy link

kzc commented Dec 12, 2021

Here's a repro using NodeJS alone with test.js above.

$ cat slowcat.js 
var fs = require("fs");
var code = fs.readFileSync(process.argv[2], "utf8");
process.stdout.write(code.substr(0, 2048));
process.stdout.write(code.substr(2048));
$ shasum test.js 
2d223992c9dbc473678f506565a7bb25b9b58559  test.js
$ node slowcat.js test.js | shasum
2d223992c9dbc473678f506565a7bb25b9b58559  -
$ node slowcat.js test.js | node-v8.0.0
null NaN 6 3 Infinity NaN undefined
$ node slowcat.js test.js | node-v10.4.1 
null NaN 6 3 Infinity NaN undefined
$ node slowcat.js test.js | node-v12.13.0 
null NaN 6 3 Infinity NaN undefined
RangeError [ERR_INVALID_ASYNC_ID]: Invalid asyncId value: undefined
    at validateAsyncId (internal/async_hooks.js:119:16)
    at emitBeforeScript (internal/async_hooks.js:349:3)
    at processTicksAndRejections (internal/process/task_queues.js:70:7)
$ node slowcat.js test.js | node-v14.16.0 
null NaN 6 3 Infinity NaN undefined
internal/process/task_queues.js:75
          callback();
          ^

TypeError: callback is not a function
    at processTicksAndRejections (internal/process/task_queues.js:75:11)
$ node slowcat.js test.js | node-v16.1.0 
null NaN 6 3 Infinity NaN undefined
node:internal/process/task_queues:78
          callback();
          ^

TypeError: callback is not a function
    at processTicksAndRejections (node:internal/process/task_queues:78:11)

Notice that if the program is altered to output the file in one chunk, it works correctly:

$ cat slowcat2.js 
var fs = require("fs");
var code = fs.readFileSync(process.argv[2], "utf8");
process.stdout.write(code.substr(0));
$ node slowcat2.js test.js | shasum
2d223992c9dbc473678f506565a7bb25b9b58559  -
$ shasum test.js
2d223992c9dbc473678f506565a7bb25b9b58559  test.js
$ node slowcat2.js test.js | node-v16.1.0 
null NaN 6 3 Infinity NaN undefined

@Trott
Copy link
Member

Trott commented Dec 12, 2021

This is not a setImmediate() problem, I don't think, because removing setImmediate from the code (and replacing it with a noop function) doesn't cause the problem to go away....

@alexlamsl alexlamsl changed the title valid use of setImmediate causes TypeError: callback is not a function chunked piping to stdin causes node to crash Dec 12, 2021
@Trott
Copy link
Member

Trott commented Dec 12, 2021

I'm not even sure it's chunked piping because I can get it to fail frequently (but not always--run it many times) with a pretty small input like this:

var _calls_ = 0, a = 0, b = 10;

function f0(foo, bar) {
    --b + (~((foo && (foo[--b + (foo && typeof foo.set == "function" && --_calls_ >= 0 && foo.set())] = (3 > 2 || 22) ^ Infinity + 0)) / (([] & [ , 0 ][1]) << (38..toString() == -2))));
}

f0([ ].__proto__, --b + ([ , a ] = []));

console.log('done');

Running that through uglify results in just 238 characters.

var _calls_=0,a=0,b=10;function f0(foo,bar){--b+~((foo&&(foo[--b+(foo&&typeof foo.set=="function"&&--_calls_>=0&&foo.set())]=(3>2||22)^Infinity+0))/(([]&[,0][1])<<(38..toString()==-2)))}f0([].__proto__,--b+([,a]=[]));console.log("done");

@Trott
Copy link
Member

Trott commented Dec 12, 2021

(And I can create vastly larger output that doesn't cause a problem at all.)

@Trott
Copy link
Member

Trott commented Dec 12, 2021

Removing console.log() call from the bottom of the input file makes it work reliably. Even from the big long original input. So it's something to do with that (or a bug in UglifyJS possibly).

@nodejs/console

@Trott
Copy link
Member

Trott commented Dec 12, 2021

OK, I think the problem here is that the code mutates Array prototype, causing internal Node.js functions to throw.

This is a good instance of what primordials and related topics are trying to prevent, but we're not sure we're going to do that for things like this or not. See nodejs/TSC#1104 for current conversation around these topics.

I think we can close this as "working as expected--mutate the Array's prototype and weird stuff is going to happen".

@Trott
Copy link
Member

Trott commented Dec 12, 2021

Here's what I've come up with as a minimal reproduction:

[].__proto__[7] = 1;

console.log('done');

It only throws the error (most of the time) in a pipeline because console flows (or buffers or something) or doesn't based on whether it detects if it's in a pipeline or tty or whatever. I forget the details, maybe someone else can fill them in. But I'm going to close this at this point. Feel free to comment or re-open if you think it deserves more discussion. But I think "hey, I modified [].__proto__ and now Node.js crashed" is a specific instance of the type of thing being discussed in nodejs/TSC#1104.

@alexlamsl
Copy link
Author

@Trott thanks for narrowing the test case down − another thing to teach our fuzzer to overlook 👌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants