Skip to content

Upgrade to libuv version 1.46.0 broke connecting to abstract unix socketsΒ #49656

Closed
@ggoodman

Description

@ggoodman

Version

>= 20.4.0

Platform

Linux b9efe00af536 5.15.49-linuxkit #1 SMP PREEMPT Tue Sep 13 07:51:32 UTC 2022 aarch64 GNU/Linux

Subsystem

net

What steps will reproduce the bug?

const http = require('http');

const SOCKET = '\0abstract';

// Create a local server to receive data from
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(
    JSON.stringify({
      data: 'Hello Worlds!',
    })
  );
});

server.listen(SOCKET, () => {
  const postData = JSON.stringify({
    msg: 'Hello World!',
  });

  const options = {
    socketPath: SOCKET,
    path: '/upload',
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Content-Length': Buffer.byteLength(postData),
    },
  };

  const req = http.request(options, (res) => {
    console.log(`STATUS: ${res.statusCode}`);
    res.setEncoding('utf8');
    res.on('data', (chunk) => {});
    res.on('end', () => {
      process.exit(0);
    });
    res.on('error', (err) => {
      console.trace(err);
      process.exit(1);
    });
  });

  req.on('error', (err) => {
    console.trace(err);
    process.exit(1);
  });

  req.end(postData);
});

server.on('error', (err) => {
  console.trace(err);
  process.exit(1);
});

If you have access to docker to facilitate cross-version testing:

for version in "12" "16" "18" "20.3" "20"; do 
  docker run --rm --volume $(pwd)/abstract_test.js:/srv/abstract_test.js "node:${version}" node /srv/abstract_test.js
done

Output:

v12.22.12 STATUS: 200
v16.20.2 STATUS: 200
v18.17.1 STATUS: 200
v20.3.1 STATUS: 200
Trace: Error: listen EINVAL: invalid argument abstract

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

It requires 100% of the time.

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

Abstract unix socket connections are possible.

What do you see instead?

See repro steps.

Additional information

In libuv/libuv#4030, we introduced uv_pipe_bind2 as a successor to uv_pipe_bind that now accepts a size_t representing the socket name length (since it starts with a NULL byte). In PipeWrap::Bind, we only ever call uv_pipe_bind:

node/src/pipe_wrap.cc

Lines 167 to 173 in 44084b8

void PipeWrap::Bind(const FunctionCallbackInfo<Value>& args) {
PipeWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
node::Utf8Value name(args.GetIsolate(), args[0]);
int err = uv_pipe_bind(&wrap->handle_, *name);
args.GetReturnValue().Set(err);
}
.

That means that the namelen argument to uv_pipe_bind2 will always default to the result of strlen(name). I suspect that strlen("\0anything") will always yield 0, resulting in EINVAL here: https://github.com/libuv/libuv/blob/0d78f3c758fdc41019093353a7ff4fed83005ac9/src/unix/pipe.c#L65-L66.

I think that the fix might be to detect strings with a leading "\0" and pass an explicit namelen to uv_pipe_bind2 in PipeWrap::Bind.

Metadata

Metadata

Assignees

No one assigned

    Labels

    libuvIssues and PRs related to the libuv dependency or the uv binding.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions