diff --git a/Makefile.am b/Makefile.am index b94fdd63..6e548a69 100644 --- a/Makefile.am +++ b/Makefile.am @@ -212,6 +212,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-pipe-server-close.c \ test/test-pipe-close-stdout-read-stdin.c \ test/test-pipe-set-non-blocking.c \ + test/test-pipe-set-fchmod.c \ test/test-platform-output.c \ test/test-poll.c \ test/test-poll-close.c \ diff --git a/docs/src/pipe.rst b/docs/src/pipe.rst index d33b0f2b..bdaeeba9 100644 --- a/docs/src/pipe.rst +++ b/docs/src/pipe.rst @@ -102,3 +102,12 @@ API and call ``uv_accept(pipe, handle)``. .. seealso:: The :c:type:`uv_stream_t` API functions also apply. + +.. c:function:: int uv_pipe_chmod(uv_pipe_t* handle, int flags) + + Alters pipe permissions, allowing it to be accessed from processes run by + different users. Makes the pipe writable or readable by all users. Mode can + be ``UV_WRITABLE``, ``UV_READABLE`` or ``UV_WRITABLE | UV_READABLE``. This + function is blocking. + + .. versionadded:: 1.16.0 diff --git a/include/uv.h b/include/uv.h index 76673e08..b8adbc9a 100644 --- a/include/uv.h +++ b/include/uv.h @@ -710,6 +710,7 @@ UV_EXTERN int uv_pipe_getpeername(const uv_pipe_t* handle, UV_EXTERN void uv_pipe_pending_instances(uv_pipe_t* handle, int count); UV_EXTERN int uv_pipe_pending_count(uv_pipe_t* handle); UV_EXTERN uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle); +UV_EXTERN int uv_pipe_chmod(uv_pipe_t* handle, int flags); struct uv_poll_s { diff --git a/src/unix/pipe.c b/src/unix/pipe.c index 4a812955..ac7cfb46 100644 --- a/src/unix/pipe.c +++ b/src/unix/pipe.c @@ -302,3 +302,56 @@ uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle) { else return uv__handle_type(handle->accepted_fd); } + + +int uv_pipe_chmod(uv_pipe_t* handle, int mode) { + unsigned desired_mode; + struct stat pipe_stat; + char* name_buffer; + size_t name_len; + int r; + + if (handle == NULL || uv__stream_fd(handle) == -1) + return -EBADF; + + if (mode != UV_READABLE && + mode != UV_WRITABLE && + mode != (UV_WRITABLE | UV_READABLE)) + return -EINVAL; + + if (fstat(uv__stream_fd(handle), &pipe_stat) == -1) + return -errno; + + desired_mode = 0; + if (mode & UV_READABLE) + desired_mode |= S_IRUSR | S_IRGRP | S_IROTH; + if (mode & UV_WRITABLE) + desired_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + /* Exit early if pipe already has desired mode. */ + if ((pipe_stat.st_mode & desired_mode) == desired_mode) + return 0; + + pipe_stat.st_mode |= desired_mode; + + /* Unfortunately fchmod does not work on all platforms, we will use chmod. */ + name_len = 0; + r = uv_pipe_getsockname(handle, NULL, &name_len); + if (r != UV_ENOBUFS) + return r; + + name_buffer = uv__malloc(name_len); + if (name_buffer == NULL) + return UV_ENOMEM; + + r = uv_pipe_getsockname(handle, name_buffer, &name_len); + if (r != 0) { + uv__free(name_buffer); + return r; + } + + r = chmod(name_buffer, pipe_stat.st_mode); + uv__free(name_buffer); + + return r != -1 ? 0 : -errno; +} diff --git a/src/win/pipe.c b/src/win/pipe.c index d102f587..642213bc 100644 --- a/src/win/pipe.c +++ b/src/win/pipe.c @@ -31,6 +31,9 @@ #include "stream-inl.h" #include "req-inl.h" +#include +#include + typedef struct uv__ipc_queue_item_s uv__ipc_queue_item_t; struct uv__ipc_queue_item_s { @@ -202,7 +205,7 @@ int uv_stdio_pipe_server(uv_loop_t* loop, uv_pipe_t* handle, DWORD access, uv_unique_pipe_name(ptr, name, nameSize); pipeHandle = CreateNamedPipeA(name, - access | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, + access | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 65536, 65536, 0, NULL); @@ -534,7 +537,7 @@ int uv_pipe_bind(uv_pipe_t* handle, const char* name) { */ handle->pipe.serv.accept_reqs[0].pipeHandle = CreateNamedPipeW(handle->name, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | - FILE_FLAG_FIRST_PIPE_INSTANCE, + FILE_FLAG_FIRST_PIPE_INSTANCE | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); @@ -803,7 +806,7 @@ static void uv_pipe_queue_accept(uv_loop_t* loop, uv_pipe_t* handle, assert(req->pipeHandle == INVALID_HANDLE_VALUE); req->pipeHandle = CreateNamedPipeW(handle->name, - PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | WRITE_DAC, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 65536, 65536, 0, NULL); @@ -2132,3 +2135,80 @@ uv_handle_type uv_pipe_pending_type(uv_pipe_t* handle) { else return UV_TCP; } + +int uv_pipe_chmod(uv_pipe_t* handle, int mode) { + SID_IDENTIFIER_AUTHORITY sid_world = SECURITY_WORLD_SID_AUTHORITY; + PACL old_dacl, new_dacl; + PSECURITY_DESCRIPTOR sd; + EXPLICIT_ACCESS ea; + PSID everyone; + int error; + + if (handle == NULL || handle->handle == INVALID_HANDLE_VALUE) + return UV_EBADF; + + if (mode != UV_READABLE && + mode != UV_WRITABLE && + mode != (UV_WRITABLE | UV_READABLE)) + return UV_EINVAL; + + if (!AllocateAndInitializeSid(&sid_world, + 1, + SECURITY_WORLD_RID, + 0, 0, 0, 0, 0, 0, 0, + &everyone)) { + error = GetLastError(); + goto done; + } + + if (GetSecurityInfo(handle->handle, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + &old_dacl, + NULL, + &sd)) { + error = GetLastError(); + goto clean_sid; + } + + memset(&ea, 0, sizeof(EXPLICIT_ACCESS)); + if (mode & UV_READABLE) + ea.grfAccessPermissions |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; + if (mode & UV_WRITABLE) + ea.grfAccessPermissions |= GENERIC_WRITE | FILE_READ_ATTRIBUTES; + ea.grfAccessPermissions |= SYNCHRONIZE; + ea.grfAccessMode = SET_ACCESS; + ea.grfInheritance = NO_INHERITANCE; + ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + ea.Trustee.ptstrName = (LPTSTR)everyone; + + if (SetEntriesInAcl(1, &ea, old_dacl, &new_dacl)) { + error = GetLastError(); + goto clean_sd; + } + + if (SetSecurityInfo(handle->handle, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + new_dacl, + NULL)) { + error = GetLastError(); + goto clean_dacl; + } + + error = 0; + +clean_dacl: + LocalFree((HLOCAL) new_dacl); +clean_sd: + LocalFree((HLOCAL) sd); +clean_sid: + FreeSid(everyone); +done: + return uv_translate_sys_error(error); +} diff --git a/test/test-list.h b/test/test-list.h index 80f01ea8..3e88e6c9 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -204,6 +204,7 @@ TEST_DECLARE (pipe_ref4) TEST_DECLARE (pipe_close_stdout_read_stdin) #endif TEST_DECLARE (pipe_set_non_blocking) +TEST_DECLARE (pipe_set_chmod) TEST_DECLARE (process_ref) TEST_DECLARE (has_ref) TEST_DECLARE (active) @@ -443,6 +444,7 @@ TASK_LIST_START TEST_ENTRY (pipe_close_stdout_read_stdin) #endif TEST_ENTRY (pipe_set_non_blocking) + TEST_ENTRY (pipe_set_chmod) TEST_ENTRY (tty) #ifdef _WIN32 TEST_ENTRY (tty_raw) diff --git a/test/test-pipe-set-fchmod.c b/test/test-pipe-set-fchmod.c new file mode 100644 index 00000000..59f0e6f5 --- /dev/null +++ b/test/test-pipe-set-fchmod.c @@ -0,0 +1,66 @@ +/* Copyright libuv project contributors. All rights reserved. +* +* 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. +*/ + + +#include "uv.h" +#include "task.h" + +TEST_IMPL(pipe_set_chmod) { + uv_pipe_t pipe_handle; + uv_loop_t* loop; + int r; + + loop = uv_default_loop(); + + r = uv_pipe_init(loop, &pipe_handle, 0); + ASSERT(r == 0); + + r = uv_pipe_bind(&pipe_handle, TEST_PIPENAME); + ASSERT(r == 0); + + /* No easy way to test if this works, we will only make sure that */ + /* the call is successful. */ + r = uv_pipe_chmod(&pipe_handle, UV_READABLE); + if (r == UV_EPERM) { + MAKE_VALGRIND_HAPPY(); + RETURN_SKIP("Insufficient privileges to alter pipe fmode"); + } + ASSERT(r == 0); + + r = uv_pipe_chmod(&pipe_handle, UV_WRITABLE); + ASSERT(r == 0); + + r = uv_pipe_chmod(&pipe_handle, UV_WRITABLE | UV_READABLE); + ASSERT(r == 0); + + r = uv_pipe_chmod(NULL, UV_WRITABLE | UV_READABLE); + ASSERT(r == UV_EBADF); + + r = uv_pipe_chmod(&pipe_handle, 12345678); + ASSERT(r == UV_EINVAL); + + uv_close((uv_handle_t*)&pipe_handle, NULL); + r = uv_pipe_chmod(&pipe_handle, UV_WRITABLE | UV_READABLE); + ASSERT(r == UV_EBADF); + + MAKE_VALGRIND_HAPPY(); + return 0; +} diff --git a/uv.gyp b/uv.gyp index 38765eef..9d9bb4b7 100644 --- a/uv.gyp +++ b/uv.gyp @@ -393,6 +393,7 @@ 'test/test-pipe-server-close.c', 'test/test-pipe-close-stdout-read-stdin.c', 'test/test-pipe-set-non-blocking.c', + 'test/test-pipe-set-fchmod.c', 'test/test-platform-output.c', 'test/test-poll.c', 'test/test-poll-close.c',