From 76147dad5ac3bb01fe3a7ecd42c3dcd88e498d13 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Tue, 15 Oct 2024 23:22:58 -0700 Subject: [PATCH] fix process-uid-gid.test.js --- src/bun.js/bindings/BunProcess.cpp | 104 +++++++ src/bun.js/bindings/ErrorCode.ts | 1 + src/bun.js/bindings/helpers.cpp | 257 +++++++++++++++++- .../test/parallel/process-uid-gid.test.js | 118 ++++++++ 4 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 test/js/node/test/parallel/process-uid-gid.test.js diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 09cf2a8fa4..1bf8679b9a 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -45,6 +45,8 @@ #include #include #include +#include +#include #else #include #include @@ -2100,6 +2102,101 @@ JSC_DEFINE_HOST_FUNCTION(Process_functiongetgroups, (JSGlobalObject * globalObje return JSValue::encode(groups); } + +static JSValue maybe_uid_by_name(JSC::ThrowScope& throwScope, JSGlobalObject* globalObject, JSValue value) +{ + if (!value.isNumber() && !value.isString()) return JSValue::decode(Bun::ERR::INVALID_ARG_TYPE(throwScope, globalObject, "id"_s, "number or string"_s, value)); + if (!value.isString()) return value; + + auto str = value.getString(globalObject); + if (!str.is8Bit()) { + auto message = makeString("User identifier does not exist: "_s, str); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message)); + return {}; + } + + auto name = (const char*)(str.span8().data()); + struct passwd pwd; + struct passwd* pp = nullptr; + char buf[8192]; + + if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { + return jsNumber(pp->pw_uid); + } + + auto message = makeString("User identifier does not exist: "_s, str); + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message)); + return {}; +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetuid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + value = maybe_uid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + Bun::V::validateInteger(scope, globalObject, value, jsString(vm, String("id"_s)), jsNumber(0), jsNumber(std::pow(2, 32))); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + auto result = setuid(id); + if (result != 0) throwSystemError(scope, globalObject, "setuid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionseteuid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + value = maybe_uid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + Bun::V::validateInteger(scope, globalObject, value, jsString(vm, String("id"_s)), jsNumber(0), jsNumber(std::pow(2, 32))); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + auto result = seteuid(id); + if (result != 0) throwSystemError(scope, globalObject, "seteuid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetegid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + value = maybe_uid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + Bun::V::validateInteger(scope, globalObject, value, jsString(vm, String("id"_s)), jsNumber(0), jsNumber(std::pow(2, 32))); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + auto result = setegid(id); + if (result != 0) throwSystemError(scope, globalObject, "setegid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetgid, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto value = callFrame->argument(0); + value = maybe_uid_by_name(scope, globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + Bun::V::validateInteger(scope, globalObject, value, jsString(vm, String("id"_s)), jsNumber(0), jsNumber(std::pow(2, 32))); + RETURN_IF_EXCEPTION(scope, {}); + auto id = value.toUInt32(globalObject); + auto result = setgid(id); + if (result != 0) throwSystemError(scope, globalObject, "setgid"_s, errno); + RETURN_IF_EXCEPTION(scope, {}); + return JSValue::encode(jsNumber(result)); +} + +JSC_DEFINE_HOST_FUNCTION(Process_functionsetgroups, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return JSValue::encode(jsNumber(0)); // TODO: +} #endif JSC_DEFINE_HOST_FUNCTION(Process_functionAssert, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -3044,12 +3141,19 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu _stopProfilerIdleNotifier Process_stubEmptyFunction Function 0 _tickCallback Process_stubEmptyFunction Function 0 _kill Process_functionReallyKill Function 2 + #if !OS(WINDOWS) getegid Process_functiongetegid Function 0 geteuid Process_functiongeteuid Function 0 getgid Process_functiongetgid Function 0 getgroups Process_functiongetgroups Function 0 getuid Process_functiongetuid Function 0 + + setegid Process_functionsetegid Function 1 + seteuid Process_functionseteuid Function 1 + setgid Process_functionsetgid Function 1 + setgroups Process_functionsetgroups Function 1 + setuid Process_functionsetuid Function 1 #endif @end */ diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index 0e9090631d..2c59f91af8 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -46,6 +46,7 @@ export default [ ["ERR_UNKNOWN_SIGNAL", TypeError, "TypeError"], ["ERR_SOCKET_BAD_PORT", RangeError, "RangeError"], ["ERR_IPC_ONE_PIPE", Error, "Error"], + ["ERR_UNKNOWN_CREDENTIAL", Error, "Error"], // Bun-specific ["ERR_FORMDATA_PARSE_ERROR", TypeError, "TypeError"], diff --git a/src/bun.js/bindings/helpers.cpp b/src/bun.js/bindings/helpers.cpp index 6aa6a6096a..ef0e106138 100644 --- a/src/bun.js/bindings/helpers.cpp +++ b/src/bun.js/bindings/helpers.cpp @@ -1,6 +1,260 @@ #include "root.h" #include "helpers.h" #include "BunClientData.h" +#include + +const char* errno_string(int errorno) +{ +#define ERRNO_CASE(e) \ + case e: \ + return #e; + switch (errorno) { +#ifdef EACCES + ERRNO_CASE(EACCES); +#endif +#ifdef EADDRINUSE + ERRNO_CASE(EADDRINUSE); +#endif +#ifdef EADDRNOTAVAIL + ERRNO_CASE(EADDRNOTAVAIL); +#endif +#ifdef EAFNOSUPPORT + ERRNO_CASE(EAFNOSUPPORT); +#endif +#ifdef EAGAIN + ERRNO_CASE(EAGAIN); +#endif +#ifdef EWOULDBLOCK +#if EAGAIN != EWOULDBLOCK + ERRNO_CASE(EWOULDBLOCK); +#endif +#endif +#ifdef EALREADY + ERRNO_CASE(EALREADY); +#endif +#ifdef EBADF + ERRNO_CASE(EBADF); +#endif +#ifdef EBADMSG + ERRNO_CASE(EBADMSG); +#endif +#ifdef EBUSY + ERRNO_CASE(EBUSY); +#endif +#ifdef ECANCELED + ERRNO_CASE(ECANCELED); +#endif +#ifdef ECHILD + ERRNO_CASE(ECHILD); +#endif +#ifdef ECONNABORTED + ERRNO_CASE(ECONNABORTED); +#endif +#ifdef ECONNREFUSED + ERRNO_CASE(ECONNREFUSED); +#endif +#ifdef ECONNRESET + ERRNO_CASE(ECONNRESET); +#endif +#ifdef EDEADLK + ERRNO_CASE(EDEADLK); +#endif +#ifdef EDESTADDRREQ + ERRNO_CASE(EDESTADDRREQ); +#endif +#ifdef EDOM + ERRNO_CASE(EDOM); +#endif +#ifdef EDQUOT + ERRNO_CASE(EDQUOT); +#endif +#ifdef EEXIST + ERRNO_CASE(EEXIST); +#endif +#ifdef EFAULT + ERRNO_CASE(EFAULT); +#endif +#ifdef EFBIG + ERRNO_CASE(EFBIG); +#endif +#ifdef EHOSTUNREACH + ERRNO_CASE(EHOSTUNREACH); +#endif +#ifdef EIDRM + ERRNO_CASE(EIDRM); +#endif +#ifdef EILSEQ + ERRNO_CASE(EILSEQ); +#endif +#ifdef EINPROGRESS + ERRNO_CASE(EINPROGRESS); +#endif +#ifdef EINTR + ERRNO_CASE(EINTR); +#endif +#ifdef EINVAL + ERRNO_CASE(EINVAL); +#endif +#ifdef EIO + ERRNO_CASE(EIO); +#endif +#ifdef EISCONN + ERRNO_CASE(EISCONN); +#endif +#ifdef EISDIR + ERRNO_CASE(EISDIR); +#endif +#ifdef ELOOP + ERRNO_CASE(ELOOP); +#endif +#ifdef EMFILE + ERRNO_CASE(EMFILE); +#endif +#ifdef EMLINK + ERRNO_CASE(EMLINK); +#endif +#ifdef EMSGSIZE + ERRNO_CASE(EMSGSIZE); +#endif +#ifdef EMULTIHOP + ERRNO_CASE(EMULTIHOP); +#endif +#ifdef ENAMETOOLONG + ERRNO_CASE(ENAMETOOLONG); +#endif +#ifdef ENETDOWN + ERRNO_CASE(ENETDOWN); +#endif +#ifdef ENETRESET + ERRNO_CASE(ENETRESET); +#endif +#ifdef ENETUNREACH + ERRNO_CASE(ENETUNREACH); +#endif +#ifdef ENFILE + ERRNO_CASE(ENFILE); +#endif +#ifdef ENOBUFS + ERRNO_CASE(ENOBUFS); +#endif +#ifdef ENODATA + ERRNO_CASE(ENODATA); +#endif +#ifdef ENODEV + ERRNO_CASE(ENODEV); +#endif +#ifdef ENOENT + ERRNO_CASE(ENOENT); +#endif +#ifdef ENOEXEC + ERRNO_CASE(ENOEXEC); +#endif +#ifdef ENOLINK + ERRNO_CASE(ENOLINK); +#endif +#ifdef ENOLCK +#if ENOLINK != ENOLCK + ERRNO_CASE(ENOLCK); +#endif +#endif +#ifdef ENOMEM + ERRNO_CASE(ENOMEM); +#endif +#ifdef ENOMSG + ERRNO_CASE(ENOMSG); +#endif +#ifdef ENOPROTOOPT + ERRNO_CASE(ENOPROTOOPT); +#endif +#ifdef ENOSPC + ERRNO_CASE(ENOSPC); +#endif +#ifdef ENOSR + ERRNO_CASE(ENOSR); +#endif +#ifdef ENOSTR + ERRNO_CASE(ENOSTR); +#endif +#ifdef ENOSYS + ERRNO_CASE(ENOSYS); +#endif +#ifdef ENOTCONN + ERRNO_CASE(ENOTCONN); +#endif +#ifdef ENOTDIR + ERRNO_CASE(ENOTDIR); +#endif +#ifdef ENOTEMPTY +#if ENOTEMPTY != EEXIST + ERRNO_CASE(ENOTEMPTY); +#endif +#endif +#ifdef ENOTSOCK + ERRNO_CASE(ENOTSOCK); +#endif +#ifdef ENOTSUP + ERRNO_CASE(ENOTSUP); +#else +#ifdef EOPNOTSUPP + ERRNO_CASE(EOPNOTSUPP); +#endif +#endif +#ifdef ENOTTY + ERRNO_CASE(ENOTTY); +#endif +#ifdef ENXIO + ERRNO_CASE(ENXIO); +#endif +#ifdef EOVERFLOW + ERRNO_CASE(EOVERFLOW); +#endif +#ifdef EPERM + ERRNO_CASE(EPERM); +#endif +#ifdef EPIPE + ERRNO_CASE(EPIPE); +#endif +#ifdef EPROTO + ERRNO_CASE(EPROTO); +#endif +#ifdef EPROTONOSUPPORT + ERRNO_CASE(EPROTONOSUPPORT); +#endif +#ifdef EPROTOTYPE + ERRNO_CASE(EPROTOTYPE); +#endif +#ifdef ERANGE + ERRNO_CASE(ERANGE); +#endif +#ifdef EROFS + ERRNO_CASE(EROFS); +#endif +#ifdef ESPIPE + ERRNO_CASE(ESPIPE); +#endif +#ifdef ESRCH + ERRNO_CASE(ESRCH); +#endif +#ifdef ESTALE + ERRNO_CASE(ESTALE); +#endif +#ifdef ETIME + ERRNO_CASE(ETIME); +#endif +#ifdef ETIMEDOUT + ERRNO_CASE(ETIMEDOUT); +#endif +#ifdef ETXTBSY + ERRNO_CASE(ETXTBSY); +#endif +#ifdef EXDEV + ERRNO_CASE(EXDEV); +#endif + + default: + return ""; + } +} JSC::JSValue createSystemError(JSC::JSGlobalObject* global, ASCIILiteral message, ASCIILiteral syscall, int err) { @@ -15,11 +269,12 @@ JSC::JSValue createSystemError(JSC::JSGlobalObject* global, ASCIILiteral message JSC::JSValue createSystemError(JSC::JSGlobalObject* global, ASCIILiteral syscall, int err) { - auto* instance = JSC::createError(global, makeString(String(syscall), "() failed"_s)); + auto* instance = JSC::createError(global, makeString(String(syscall), "() failed: "_s, String::fromLatin1(strerror(err)))); auto& vm = global->vm(); auto& builtinNames = WebCore::builtinNames(vm); instance->putDirect(vm, builtinNames.syscallPublicName(), jsString(vm, String(syscall)), 0); instance->putDirect(vm, builtinNames.errnoPublicName(), JSC::jsNumber(err), 0); instance->putDirect(vm, vm.propertyNames->name, jsString(vm, String("SystemError"_s)), JSC::PropertyAttribute::DontEnum | 0); + instance->putDirect(vm, builtinNames.codePublicName(), jsString(vm, String::fromLatin1(errno_string(err)))); return instance; } diff --git a/test/js/node/test/parallel/process-uid-gid.test.js b/test/js/node/test/parallel/process-uid-gid.test.js new file mode 100644 index 0000000000..2bd9d73300 --- /dev/null +++ b/test/js/node/test/parallel/process-uid-gid.test.js @@ -0,0 +1,118 @@ +//#FILE: test-process-uid-gid.js +//#SHA1: fdd637ef2fcf3bcada2c86f574494e32e5c03780 +//----------------- +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +"use strict"; + +const isWindows = process.platform === "win32"; +const isMainThread = !process.env.NODE_WORKER_ID; + +if (isWindows) { + test("uid/gid functions are POSIX only", () => { + // uid/gid functions are POSIX only. + expect(process.getuid).toBeUndefined(); + expect(process.getgid).toBeUndefined(); + expect(process.setuid).toBeUndefined(); + expect(process.setgid).toBeUndefined(); + }); +} else if (isMainThread) { + test("setuid with invalid arguments", () => { + expect(() => process.setuid({})).toThrow( + expect.objectContaining({ + code: "ERR_INVALID_ARG_TYPE", + message: expect.stringContaining('The "id" argument must be of type number or string'), + }), + ); + + expect(() => process.setuid("fhqwhgadshgnsdhjsdbkhsdabkfabkveyb")).toThrow( + expect.objectContaining({ + code: "ERR_UNKNOWN_CREDENTIAL", + message: "User identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb", + }), + ); + }); + + test("edge cases for uid/gid functions", () => { + // Passing -0 shouldn't crash the process + // Refs: https://github.com/nodejs/node/issues/32750 + // And neither should values exceeding 2 ** 31 - 1. + const ids = [-0, 2 ** 31, 2 ** 32 - 1]; + const fns = [process.setuid, process.setuid, process.setgid, process.setegid]; + + for (const id of ids) { + for (const fn of fns) { + expect(() => { + try { + fn(id); + } catch { + // Continue regardless of error. + } + }).not.toThrow(); + } + } + }); + + if (process.getuid() !== 0) { + test("non-root user permissions", () => { + // Should not throw. + expect(() => process.getgid()).not.toThrow(); + expect(() => process.getuid()).not.toThrow(); + + expect(() => process.setgid("nobody")).toThrow( + expect.objectContaining({ + syscall: "setgid", + code: "EPERM", + }), + ); + + expect(() => process.setuid("nobody")).toThrow( + expect.objectContaining({ + syscall: "setuid", + code: "EPERM", + }), + ); + }); + } else { + test("root user permissions", async () => { + const oldgid = process.getgid(); + try { + process.setgid("nobody"); + } catch (err) { + if (err.code !== "ERR_UNKNOWN_CREDENTIAL") { + throw err; + } + process.setgid("nogroup"); + } + + const newgid = process.getgid(); + expect(newgid).not.toBe(oldgid); + + const olduid = process.getuid(); + process.setuid("nobody"); + const newuid = process.getuid(); + expect(newuid).not.toBe(olduid); + }); + } +} + +//<#END_FILE: test-process-uid-gid.js