Compare commits

...

4 Commits

Author SHA1 Message Date
Claude Bot
b0b94b1e7a Add integer validation for numeric uid/gid arguments
- Use Bun::V::validateInteger to reject non-integers (NaN, Infinity, floats, negatives)
- Match the validation pattern used in setuid/setgid functions
- Add comprehensive test coverage for numeric edge cases (1.5, -1, NaN, Infinity, 2^31)
- Validate both user and extraGroup parameters when numeric

Addresses CodeRabbit review feedback.
2025-10-21 06:33:54 +00:00
Claude Bot
016fac7756 Use CString instead of strdup in name_by_uid
Removes memory leak by returning CString instead of raw char* with strdup.
The CString is kept alive in the function scope and properly manages memory.
2025-10-21 06:30:08 +00:00
Claude Bot
15a8c723de Move test to correct location
Tests should be in test/js/node/process/ not in the node parallel test
directory unless copied from upstream Node.js tests.
2025-10-21 06:20:35 +00:00
Claude Bot
5064b28459 Add process.initgroups implementation
Implements process.initgroups(user, extraGroup) to match Node.js behavior.
The function initializes the group access list by reading /etc/group and
adding all groups the user belongs to.

Implementation details:
- Accepts user as string (username) or number (uid)
- Accepts extraGroup as string (group name) or number (gid)
- Validates user exists before calling initgroups
- Properly handles memory management for uid-to-username conversion
- Returns appropriate ERR_UNKNOWN_CREDENTIAL errors for invalid users/groups
- Only available on POSIX platforms (not Windows)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 06:18:06 +00:00
2 changed files with 160 additions and 0 deletions

View File

@@ -2760,6 +2760,96 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionsetgroups, (JSGlobalObject * globalObje
return JSValue::encode(jsNumber(result));
}
static CString name_by_uid(uid_t uid)
{
struct passwd pwd;
struct passwd* pp = nullptr;
char buf[8192];
if (getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) {
return CString(pp->pw_name);
}
return CString();
}
JSC_DEFINE_HOST_FUNCTION(Process_functioninitgroups, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto userArg = callFrame->argument(0);
auto extraGroupArg = callFrame->argument(1);
// Validate arguments
if (!userArg.isNumber() && !userArg.isString()) {
return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "user"_s, "number or string"_s, userArg);
}
if (!extraGroupArg.isNumber() && !extraGroupArg.isString()) {
return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, "extraGroup"_s, "number or string"_s, extraGroupArg);
}
CString userString;
const char* user = nullptr;
// Get username
if (userArg.isNumber()) {
uint32_t uid = 0;
Bun::V::validateInteger(scope, globalObject, userArg, "user"_s, jsNumber(0), jsNumber(std::pow(2, 31) - 1), &uid);
RETURN_IF_EXCEPTION(scope, {});
userString = name_by_uid(static_cast<uid_t>(uid));
if (userString.isNull()) {
auto message = makeString("User identifier does not exist: "_s, uid);
scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message));
return {};
}
user = userString.data();
} else {
auto str = userArg.getString(globalObject);
RETURN_IF_EXCEPTION(scope, {});
userString = str.utf8();
// Verify the user exists first
struct passwd pwd;
struct passwd* pp = nullptr;
char buf[8192];
if (getpwnam_r(userString.data(), &pwd, buf, sizeof(buf), &pp) != 0 || pp == nullptr) {
auto message = makeString("User identifier does not exist: "_s, str);
scope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_UNKNOWN_CREDENTIAL, message));
return {};
}
user = userString.data();
}
// Get extra group
gid_t extra_group = 0;
if (extraGroupArg.isNumber()) {
uint32_t gid = 0;
Bun::V::validateInteger(scope, globalObject, extraGroupArg, "extraGroup"_s, jsNumber(0), jsNumber(std::pow(2, 31) - 1), &gid);
RETURN_IF_EXCEPTION(scope, {});
extra_group = static_cast<gid_t>(gid);
} else {
auto extraGroupValue = maybe_gid_by_name(scope, globalObject, extraGroupArg);
RETURN_IF_EXCEPTION(scope, {});
extra_group = static_cast<gid_t>(extraGroupValue.toUInt32(globalObject));
RETURN_IF_EXCEPTION(scope, {});
}
// Call initgroups
int rc = initgroups(user, extra_group);
if (rc != 0) {
throwSystemError(scope, globalObject, "initgroups"_s, errno);
return {};
}
return JSValue::encode(jsUndefined());
}
#endif
JSC_DEFINE_HOST_FUNCTION(Process_functionAssert, (JSGlobalObject * globalObject, CallFrame* callFrame))
@@ -3935,6 +4025,7 @@ extern "C" void Process__emitErrorEvent(Zig::GlobalObject* global, EncodedJSValu
getgroups Process_functiongetgroups Function 0
getuid Process_functiongetuid Function 0
initgroups Process_functioninitgroups Function 2
setegid Process_functionsetegid Function 1
seteuid Process_functionseteuid Function 1
setgid Process_functionsetgid Function 1

View File

@@ -0,0 +1,69 @@
import { expect, test } from "bun:test";
import { isWindows } from "harness";
if (!isWindows) {
test("process.initgroups validates arguments", () => {
// Missing arguments
expect(() => {
// @ts-expect-error
process.initgroups();
}).toThrow();
expect(() => {
// @ts-expect-error
process.initgroups("user");
}).toThrow();
// Invalid argument types
const invalidValues = [null, true, {}, [], () => {}];
for (const val of invalidValues) {
expect(() => {
// @ts-expect-error
process.initgroups(val, 1000);
}).toThrow();
expect(() => {
// @ts-expect-error
process.initgroups("user", val);
}).toThrow();
}
});
test("process.initgroups validates numeric arguments are integers", () => {
// Non-integer numeric values should be rejected
const invalidNumericValues = [1.5, -1, NaN, Infinity, -Infinity, 2 ** 31];
for (const val of invalidNumericValues) {
expect(() => {
process.initgroups(val, 1000);
}).toThrow();
expect(() => {
process.initgroups("root", val);
}).toThrow();
}
});
test("process.initgroups throws for non-existent user", () => {
expect(() => {
process.initgroups("fhqwhgadshgnsdhjsdbkhsdabkfabkveyb", 1000);
}).toThrow(/User identifier does not exist/);
});
test("process.initgroups throws for non-existent group", () => {
expect(() => {
process.initgroups("root", "fhqwhgadshgnsdhjsdbkhsdabkfabkveyb");
}).toThrow(/Group identifier does not exist/);
});
test("process.initgroups throws for invalid uid", () => {
expect(() => {
process.initgroups(9999999, 1000);
}).toThrow(/User identifier does not exist/);
});
} else {
test("process.initgroups is undefined on Windows", () => {
expect(process.initgroups).toBeUndefined();
});
}