diff --git a/docs/src/process.rst b/docs/src/process.rst index c0c477ef..2b09a93a 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) @@ -94,7 +96,20 @@ Data types * search for the exact file name before trying variants with * extensions like '.exe' or '.cmd'. */ - UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7) + UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME = (1 << 7), + /* + * Spawn the child process with the error mode of its parent. + * This option is only meaningful on Windows systems. On Unix + * it is silently ignored. + */ + 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 'num_gids' field. This does not work on windows; + * setting this flag will cause uv_spawn() to fail. + */ + UV_PROCESS_SETGROUPS = (1 << 9) }; .. c:type:: uv_stdio_container_t @@ -221,14 +236,18 @@ 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_gid_t* uv_process_options_t.gids +.. c:member:: size_t 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 to ``UV_ENOTSUP``. + .. versionadded:: 2.0.0 + .. c:member:: char* uv_process_options_t.cpumask .. c:member:: size_t uv_process_options_t.cpumask_size diff --git a/include/uv.h b/include/uv.h index d59bf75e..e3233c0f 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,8 @@ 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 +1166,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 +1214,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 'num_gids' 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..dd569bb3 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -395,6 +395,12 @@ static void uv__process_child_init(const uv_process_options_t* options, SAVE_ERRNO(setgroups(0, NULL)); } + if (options->flags & UV_PROCESS_SETGROUPS) { + if (setgroups(options->gids_size, options->gids)) { + uv__write_errno(error_fd); + } + } + if ((options->flags & UV_PROCESS_SETGID) && setgid(options->gid)) uv__write_errno(error_fd); @@ -1022,6 +1028,7 @@ int uv_spawn(uv_loop_t* loop, assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID | + UV_PROCESS_SETGROUPS | UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME | UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | diff --git a/src/win/process.c b/src/win/process.c index 92caf1f2..2f107321 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,6 +920,7 @@ int uv_spawn(uv_loop_t* loop, assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID | + UV_PROCESS_SETGROUPS | UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME | UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_HIDE_CONSOLE | diff --git a/test/test-list.h b/test/test-list.h index 93e9f17d..e1735df1 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -343,6 +343,7 @@ TEST_DECLARE (spawn_setuid_fails) TEST_DECLARE (spawn_setgid_fails) TEST_DECLARE (spawn_affinity) TEST_DECLARE (spawn_affinity_invalid_mask) +TEST_DECLARE (spawn_setgids_fails) TEST_DECLARE (spawn_stdout_to_file) TEST_DECLARE (spawn_stdout_and_stderr_to_file) TEST_DECLARE (spawn_stdout_and_stderr_to_file2) @@ -541,6 +542,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) @@ -1039,6 +1041,7 @@ TASK_LIST_START TEST_ENTRY (spawn_setgid_fails) TEST_ENTRY (spawn_affinity) TEST_ENTRY (spawn_affinity_invalid_mask) + TEST_ENTRY (spawn_setgids_fails) TEST_ENTRY (spawn_stdout_to_file) TEST_ENTRY (spawn_stdout_and_stderr_to_file) TEST_ENTRY (spawn_stdout_and_stderr_to_file2) @@ -1084,6 +1087,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..0e17892b 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -1498,6 +1498,44 @@ 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) + RETURN_SKIP("spawn_setgids skipped: not root\n"); + + init_process_options("spawn_helper1", exit_cb); + + pw = getpwnam("nobody"); + ASSERT(pw != NULL); + gids[0] = pw->pw_gid; + options.gids = gids; + options.gids_size = 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(uv_default_loop()); + return 0; +} +#endif + #ifndef _WIN32 TEST_IMPL(spawn_setuid_fails) { @@ -1594,6 +1632,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_size = 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(uv_default_loop()); + return 0; +} #endif TEST_IMPL(spawn_affinity) { @@ -1757,6 +1828,27 @@ 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}; + init_process_options("spawn_helper1", exit_cb_unexpected); + + options.flags |= UV_PROCESS_SETGROUPS; + options.gids = gids; + options.gids_size = 1; + + r = uv_spawn(uv_default_loop(), &process, &options); + ASSERT_EQ(r, UV_ENOTSUP); + + r = uv_run(uv_default_loop(), UV_RUN_DEFAULT); + ASSERT_OK(r); + + ASSERT_OK(close_cb_called); + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} #endif