diff --git a/docs/src/process.rst b/docs/src/process.rst index c0c477ef..a3d160b6 100644 --- a/docs/src/process.rst +++ b/docs/src/process.rst @@ -34,6 +34,8 @@ Data types uv_gid_t gid; char* cpumask; size_t cpumask_size; + uv_gid_t* gids; + size_t gids_size; } uv_process_options_t; .. c:type:: void (*uv_exit_cb)(uv_process_t*, int64_t exit_status, int term_signal) @@ -95,6 +97,13 @@ Data types * extensions like '.exe' or '.cmd'. */ UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7) + /* + * Set the child process' supplementary group ids. The group ids are supplied + * in the 'gids' field in the options struct, and the number of groups is + * specified in the 'gids_sz' field. This does not work on windows; + * setting this flag will cause uv_spawn() to fail. + */ + UV_PROCESS_SETGROUPS = (1 << 8) }; .. c:type:: uv_stdio_container_t @@ -221,9 +230,11 @@ Public members .. c:member:: uv_uid_t uv_process_options_t.uid .. c:member:: uv_gid_t uv_process_options_t.gid +.. c:member:: uv_process_options_t.gids +.. c:member:: uv_process_options_t.gids_size - Libuv can change the child process' user/group id. This happens only when - the appropriate bits are set in the flags fields. + Libuv can change the child process' user/group id and supplementary group + ids. This happens only when the appropriate bits are set in the flags fields. .. note:: This is not supported on Windows, :c:func:`uv_spawn` will fail and set the error diff --git a/include/uv.h b/include/uv.h index d59bf75e..3e5a25a7 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1131,9 +1131,10 @@ typedef struct uv_process_options_s { int stdio_count; uv_stdio_container_t* stdio; /* - * Libuv can change the child process' user/group id. This happens only when - * the appropriate bits are set in the flags fields. This is not supported on - * windows; uv_spawn() will fail and set the error to UV_ENOTSUP. + * Libuv can change the child process' user/group id, and supplementarty + * group ids. This happens only when the appropriate bits are set in the + * flags fields. This is not supported on windows; uv_spawn() will fail and + * set the error to UV_ENOTSUP. */ uv_uid_t uid; uv_gid_t gid; @@ -1150,6 +1151,9 @@ typedef struct uv_process_options_s { */ char* cpumask; size_t cpumask_size; + uv_gid_t* gids; + size_t gids_size; + } uv_process_options_t; /* @@ -1163,7 +1167,7 @@ enum uv_process_flags { */ UV_PROCESS_SETUID = (1 << 0), /* - * Set the child process' group id. The user id is supplied in the `gid` + * Set the child process' group id. The group id is supplied in the `gid` * field of the options struct. This does not work on windows; setting this * flag will cause uv_spawn() to fail. */ @@ -1211,7 +1215,14 @@ enum uv_process_flags { * This option is only meaningful on Windows systems. On Unix * it is silently ignored. */ - UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE = (1 << 8) + UV_PROCESS_WINDOWS_USE_PARENT_ERROR_MODE = (1 << 8), + /* + * Set the child process' supplementary group ids. The group ids are supplied + * in the 'gids' field in the options struct, and the number of groups is + * specified in the 'gids_sz' field. This does not work on windows; + * setting this flag will cause uv_spawn() to fail on windows. + */ + UV_PROCESS_SETGROUPS = (1 << 9) }; /* diff --git a/src/unix/process.c b/src/unix/process.c index 30b0b33e..850a531d 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -395,8 +395,17 @@ static void uv__process_child_init(const uv_process_options_t* options, SAVE_ERRNO(setgroups(0, NULL)); } - if ((options->flags & UV_PROCESS_SETGID) && setgid(options->gid)) - uv__write_errno(error_fd); + if (options->flags & UV_PROCESS_SETGROUPS) { + if (setgroups(options->gids_size, options->gids)) { + uv__write_int(error_fd, -errno); + _exit(127); + } + } + + if ((options->flags & UV_PROCESS_SETGID) && setgid(options->gid)) { + uv__write_int(error_fd, -errno); + _exit(127); + } if ((options->flags & UV_PROCESS_SETUID) && setuid(options->uid)) uv__write_errno(error_fd); @@ -899,6 +908,13 @@ static int uv__spawn_and_init_child( int exec_errorno; ssize_t r; + assert(options->file != NULL); + assert(!(options->flags & ~(UV_PROCESS_DETACHED | + UV_PROCESS_SETGID | + UV_PROCESS_SETUID | + UV_PROCESS_SETGROUPS | + UV_PROCESS_WINDOWS_HIDE | + UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); #if defined(__APPLE__) uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn); diff --git a/src/win/process.c b/src/win/process.c index 92caf1f2..215a2509 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -899,7 +899,9 @@ int uv_spawn(uv_loop_t* loop, process->exit_cb = options->exit_cb; child_stdio_buffer = NULL; - if (options->flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) { + if (options->flags & (UV_PROCESS_SETGID | + UV_PROCESS_SETUID | + UV_PROCESS_SETGROUPS)) { return UV_ENOTSUP; } @@ -918,7 +920,11 @@ int uv_spawn(uv_loop_t* loop, assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID | +<<<<<<< HEAD UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME | +======= + UV_PROCESS_SETGROUPS | +>>>>>>> 20a263fd (unix: Support setting supplementary process groups) UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | UV_PROCESS_WINDOWS_HIDE_GUI | diff --git a/test/test-list.h b/test/test-list.h index 93e9f17d..c70b956c 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -341,8 +341,12 @@ TEST_DECLARE (spawn_preserve_env) TEST_DECLARE (spawn_same_stdout_stderr) TEST_DECLARE (spawn_setuid_fails) TEST_DECLARE (spawn_setgid_fails) +<<<<<<< HEAD TEST_DECLARE (spawn_affinity) TEST_DECLARE (spawn_affinity_invalid_mask) +======= +TEST_DECLARE (spawn_setgids_fails) +>>>>>>> 20a263fd (unix: Support setting supplementary process groups) TEST_DECLARE (spawn_stdout_to_file) TEST_DECLARE (spawn_stdout_and_stderr_to_file) TEST_DECLARE (spawn_stdout_and_stderr_to_file2) @@ -541,6 +545,7 @@ TEST_DECLARE (win32_signum_number) #else TEST_DECLARE (emfile) TEST_DECLARE (spawn_setuid_setgid) +TEST_DECLARE (spawn_setgids) TEST_DECLARE (we_get_signal) TEST_DECLARE (we_get_signals) TEST_DECLARE (we_get_signal_one_shot) @@ -1037,8 +1042,12 @@ TASK_LIST_START TEST_ENTRY (spawn_same_stdout_stderr) TEST_ENTRY (spawn_setuid_fails) TEST_ENTRY (spawn_setgid_fails) +<<<<<<< HEAD TEST_ENTRY (spawn_affinity) TEST_ENTRY (spawn_affinity_invalid_mask) +======= + TEST_ENTRY (spawn_setgids_fails) +>>>>>>> 20a263fd (unix: Support setting supplementary process groups) TEST_ENTRY (spawn_stdout_to_file) TEST_ENTRY (spawn_stdout_and_stderr_to_file) TEST_ENTRY (spawn_stdout_and_stderr_to_file2) @@ -1084,6 +1093,7 @@ TASK_LIST_START #else TEST_ENTRY (emfile) TEST_ENTRY (spawn_setuid_setgid) + TEST_ENTRY (spawn_setgids) TEST_ENTRY (we_get_signal) TEST_ENTRY (we_get_signals) TEST_ENTRY (we_get_signal_one_shot) diff --git a/test/test-spawn.c b/test/test-spawn.c index 0ee7b91d..89df8d57 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -1498,6 +1498,46 @@ TEST_IMPL(spawn_setuid_setgid) { } #endif +#ifndef _WIN32 +TEST_IMPL(spawn_setgids) { + int r; + struct passwd* pw; + uv_gid_t gids[1]; + + /* if not root, then this will fail. */ + uv_uid_t uid = getuid(); + if (uid != 0) { + fprintf(stderr, "spawn_setgids skipped: not root\n"); + return 0; + } + + init_process_options("spawn_helper1", exit_cb); + + pw = getpwnam("nobody"); + ASSERT(pw != NULL); + gids[0] = pw->pw_gid; + options.gids = gids; + options.gids_sz = 1; + + options.flags = UV_PROCESS_SETGROUPS; + + r = uv_spawn(uv_default_loop(), &process, &options); + if (r == UV_EACCES) + RETURN_SKIP("user 'nobody' cannot access the test runner"); + + ASSERT(r == 0); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 1); + + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif + #ifndef _WIN32 TEST_IMPL(spawn_setuid_fails) { @@ -1594,6 +1634,39 @@ TEST_IMPL(spawn_setgid_fails) { MAKE_VALGRIND_HAPPY(uv_default_loop()); return 0; } + + +TEST_IMPL(spawn_setgids_fails) { + int r; + uv_gid_t gids[1] = {0}; + + /* if root, become nobody. */ + uv_uid_t uid = getuid(); + if (uid == 0) { + struct passwd* pw; + pw = getpwnam("nobody"); + ASSERT(pw != NULL); + ASSERT(0 == setgid(pw->pw_gid)); + ASSERT(0 == setuid(pw->pw_uid)); + } + + init_process_options("spawn_helper1", fail_cb); + + options.flags |= UV_PROCESS_SETGROUPS; + options.gids = gids; + options.gids_sz = 1; + + r = uv_spawn(uv_default_loop(), &process, &options); + ASSERT(r == UV_EPERM); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT(r == 0); + + ASSERT(close_cb_called == 0); + + MAKE_VALGRIND_HAPPY(); + return 0; +} #endif TEST_IMPL(spawn_affinity) {