diff options
| -rw-r--r-- | docs/man/nng_ipc.7.adoc | 26 | ||||
| -rw-r--r-- | src/core/platform.h | 19 | ||||
| -rw-r--r-- | src/platform/posix/posix_aio.h | 3 | ||||
| -rw-r--r-- | src/platform/posix/posix_epdesc.c | 55 | ||||
| -rw-r--r-- | src/platform/posix/posix_ipc.c | 16 | ||||
| -rw-r--r-- | src/platform/posix/posix_tcp.c | 2 | ||||
| -rw-r--r-- | src/platform/windows/win_ipc.c | 52 | ||||
| -rw-r--r-- | src/transport/ipc/ipc.c | 47 | ||||
| -rw-r--r-- | src/transport/ipc/ipc.h | 16 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | tests/ipcperms.c | 114 | ||||
| -rw-r--r-- | tests/ipcwinsec.c | 200 |
12 files changed, 529 insertions, 23 deletions
diff --git a/docs/man/nng_ipc.7.adoc b/docs/man/nng_ipc.7.adoc index ee89b29a..199aa757 100644 --- a/docs/man/nng_ipc.7.adoc +++ b/docs/man/nng_ipc.7.adoc @@ -45,7 +45,7 @@ This transport uses URIs using the scheme `ipc://`, followed by a an absolute path name in the file system where the socket or named pipe should be created. -TIP: On Windows, all names are prefixed by `\.\pipe\` and do not +TIP: On Windows, all names are prefixed by `\\.\pipe\` and do not occupy the normal file system. On POSIX platforms, the path is taken literally, and is relative to the root directory. @@ -63,9 +63,29 @@ the actual structure is of type <<nng_sockaddr_ipc.5#,`nng_sockaddr_ipc`>>. === Transport Options -The _ipc_ transport has no special options. +((`NNG_OPT_IPC_PERMISSIONS`)):: -NOTE: Options for security attributes and credentials are planned. +This write-only option may be used to configure the permissions that +are used on the UNIX domain socket created by a listener. +This property is only supported on POSIX systems. +The value is of type `int`, representing the normal permission bits +on a file, such as `0600` (typically meaning read-write to the owner, and +no permissions for anyone else.) +The default is system-specific, most often `0644`. + +NOTE: Normally both read and write permission will be necessary for a +peer dialer to connect. +See your system documentation for UNIX domain sockets for more information. + +NOTE: The _umask_ of the process is *not* applied to these bits. + +((`NNG_OPT_IPC_SECURITY_DESCRIPTOR`)):: + +This write-only option may be used on Windows platforms to configure +the `SECURITY_DESCRIPTOR` that is used when creating the underying +named pipe. +The value is a pointer, `PSECURITY_DESCRIPTOR`, and may only be +applied to listeners that have not been started yet. == SEE ALSO diff --git a/src/core/platform.h b/src/core/platform.h index d6191038..671556d8 100644 --- a/src/core/platform.h +++ b/src/core/platform.h @@ -284,6 +284,8 @@ typedef struct nni_plat_ipc_ep nni_plat_ipc_ep; typedef struct nni_plat_ipc_pipe nni_plat_ipc_pipe; // nni_plat_ipc_ep_init creates a new endpoint associated with the url. +// The final field is the mode, either for dialing (NNI_EP_MODE_DIAL) or +// listening (NNI_EP_MODE_LISTEN). extern int nni_plat_ipc_ep_init(nni_plat_ipc_ep **, const nni_sockaddr *, int); // nni_plat_ipc_ep_fini closes the endpoint and releases resources. @@ -306,6 +308,23 @@ extern void nni_plat_ipc_ep_accept(nni_plat_ipc_ep *, nni_aio *); // An accepted connection will be passed back in the a_pipe member. extern void nni_plat_ipc_ep_connect(nni_plat_ipc_ep *, nni_aio *); +// nni_plat_ipc_ep_set_security_descriptor sets the Windows security +// descriptor. This is *only* supported for Windows platforms. All +// others return NNG_ENOTSUP. The void argument is a pointer to +// a SECURITY_DESCRIPTOR object, and must be valid. +extern int nni_plat_ipc_ep_set_security_descriptor(nni_plat_ipc_ep *, void *); + +// nni_plat_ipc_ep_set_permissions sets UNIX style permissions +// on the named pipes. This basically just does a chmod() on the +// named pipe, and is only supported o the server side, and only on +// systems that support this (POSIX, not Windows). Note that changing +// ownership is not supported at this time. Most systems use only +// 16-bits, the lower 12 of which are user, group, and other, e.g. +// 0640 gives read/write access to user, read to group, and prevents +// any other user from accessing it. This option only has meaning +// for listeners, on dialers it is ignored. +extern int nni_plat_ipc_ep_set_permissions(nni_plat_ipc_ep *, uint32_t); + // nni_plat_ipc_pipe_fini closes the pipe, and releases all resources // associated with it. extern void nni_plat_ipc_pipe_fini(nni_plat_ipc_pipe *); diff --git a/src/platform/posix/posix_aio.h b/src/platform/posix/posix_aio.h index 15dae06c..3954f225 100644 --- a/src/platform/posix/posix_aio.h +++ b/src/platform/posix/posix_aio.h @@ -34,7 +34,7 @@ extern int nni_posix_pipedesc_sockname(nni_posix_pipedesc *, nni_sockaddr *); extern int nni_posix_pipedesc_set_nodelay(nni_posix_pipedesc *, bool); extern int nni_posix_pipedesc_set_keepalive(nni_posix_pipedesc *, bool); -extern int nni_posix_epdesc_init(nni_posix_epdesc **); +extern int nni_posix_epdesc_init(nni_posix_epdesc **, int); extern void nni_posix_epdesc_set_local(nni_posix_epdesc *, void *, size_t); extern void nni_posix_epdesc_set_remote(nni_posix_epdesc *, void *, size_t); extern void nni_posix_epdesc_fini(nni_posix_epdesc *); @@ -43,5 +43,6 @@ extern void nni_posix_epdesc_connect(nni_posix_epdesc *, nni_aio *); extern int nni_posix_epdesc_listen(nni_posix_epdesc *); extern void nni_posix_epdesc_accept(nni_posix_epdesc *, nni_aio *); extern int nni_posix_epdesc_sockname(nni_posix_epdesc *, nni_sockaddr *); +extern int nni_posix_epdesc_set_permissions(nni_posix_epdesc *, mode_t); #endif // PLATFORM_POSIX_AIO_H diff --git a/src/platform/posix/posix_epdesc.c b/src/platform/posix/posix_epdesc.c index f97f12b8..0f63304f 100644 --- a/src/platform/posix/posix_epdesc.c +++ b/src/platform/posix/posix_epdesc.c @@ -22,6 +22,7 @@ #include <stdlib.h> #include <string.h> #include <sys/socket.h> +#include <sys/stat.h> #include <sys/types.h> #include <sys/uio.h> #include <sys/un.h> @@ -38,10 +39,13 @@ struct nni_posix_epdesc { nni_list connectq; nni_list acceptq; bool closed; + bool started; struct sockaddr_storage locaddr; struct sockaddr_storage remaddr; socklen_t loclen; socklen_t remlen; + mode_t perms; // UNIX sockets only + int mode; // end point mode (dialer/listener) nni_mtx mtx; }; @@ -287,17 +291,35 @@ nni_posix_epdesc_listen(nni_posix_epdesc *ed) #endif if (bind(fd, (struct sockaddr *) ss, len) < 0) { - nni_mtx_unlock(&ed->mtx); rv = nni_plat_errno(errno); + nni_mtx_unlock(&ed->mtx); (void) close(fd); return (rv); } + // For UNIX domain sockets, optionally set the permission bits. + // This is done after the bind and before listen, and on the file + // rather than the file descriptor. + // Experiments have shown that chmod() works correctly, provided that + // it is done *before* the listen() operation, whereas fchmod seems to + // have no impact. This behavior was observed on both macOS and Linux. + // YMMV on other platforms. + if ((ss->ss_family == AF_UNIX) && (ed->perms != 0)) { + struct sockaddr_un *sun = (void *) ss; + mode_t perms = ed->perms & ~(S_IFMT); + if ((rv = chmod(sun->sun_path, perms)) != 0) { + rv = nni_plat_errno(errno); + nni_mtx_unlock(&ed->mtx); + close(fd); + return (rv); + } + } + // Listen -- 128 depth is probably sufficient. If it isn't, other // bad things are going to happen. if (listen(fd, 128) != 0) { - nni_mtx_unlock(&ed->mtx); rv = nni_plat_errno(errno); + nni_mtx_unlock(&ed->mtx); (void) close(fd); return (rv); } @@ -311,6 +333,7 @@ nni_posix_epdesc_listen(nni_posix_epdesc *ed) nni_mtx_unlock(&ed->mtx); return (rv); } + ed->started = true; nni_mtx_unlock(&ed->mtx); return (0); } @@ -384,6 +407,7 @@ nni_posix_epdesc_connect(nni_posix_epdesc *ed, nni_aio *aio) if ((rv = connect(fd, (void *) &ed->remaddr, ed->remlen)) == 0) { // Immediate connect, cool! This probably only happens on // loopback, and probably not on every platform. + ed->started = true; nni_posix_epdesc_finish(aio, 0, fd); nni_mtx_unlock(&ed->mtx); return; @@ -403,6 +427,7 @@ nni_posix_epdesc_connect(nni_posix_epdesc *ed, nni_aio *aio) // We have to submit to the pollq, because the connection is pending. ed->node.fd = fd; + ed->started = true; if ((rv = nni_posix_pollq_add(&ed->node)) != 0) { ed->node.fd = -1; nni_mtx_unlock(&ed->mtx); @@ -418,7 +443,7 @@ nni_posix_epdesc_connect(nni_posix_epdesc *ed, nni_aio *aio) } int -nni_posix_epdesc_init(nni_posix_epdesc **edp) +nni_posix_epdesc_init(nni_posix_epdesc **edp, int mode) { nni_posix_epdesc *ed; int rv; @@ -439,6 +464,9 @@ nni_posix_epdesc_init(nni_posix_epdesc **edp) ed->node.data = ed; ed->node.fd = -1; ed->closed = false; + ed->started = false; + ed->perms = 0; // zero means use default (no change) + ed->mode = mode; nni_aio_list_init(&ed->connectq); nni_aio_list_init(&ed->acceptq); @@ -476,6 +504,27 @@ nni_posix_epdesc_set_remote(nni_posix_epdesc *ed, void *sa, size_t len) nni_mtx_unlock(&ed->mtx); } +int +nni_posix_epdesc_set_permissions(nni_posix_epdesc *ed, mode_t mode) +{ + nni_mtx_lock(&ed->mtx); + if (ed->mode != NNI_EP_MODE_LISTEN) { + nni_mtx_unlock(&ed->mtx); + return (NNG_ENOTSUP); + } + if (ed->started) { + nni_mtx_unlock(&ed->mtx); + return (NNG_EBUSY); + } + if ((mode & S_IFMT) != 0) { + nni_mtx_unlock(&ed->mtx); + return (NNG_EINVAL); + } + ed->perms = mode | S_IFSOCK; // we set IFSOCK to ensure non-zero + nni_mtx_unlock(&ed->mtx); + return (0); +} + void nni_posix_epdesc_fini(nni_posix_epdesc *ed) { diff --git a/src/platform/posix/posix_ipc.c b/src/platform/posix/posix_ipc.c index f7cfc0db..c1bb9292 100644 --- a/src/platform/posix/posix_ipc.c +++ b/src/platform/posix/posix_ipc.c @@ -49,7 +49,7 @@ nni_plat_ipc_ep_init(nni_plat_ipc_ep **epp, const nni_sockaddr *sa, int mode) int rv; struct sockaddr_un sun; - if ((rv = nni_posix_epdesc_init(&ed)) != 0) { + if ((rv = nni_posix_epdesc_init(&ed, mode)) != 0) { return (rv); } switch (mode) { @@ -87,6 +87,20 @@ nni_plat_ipc_ep_close(nni_plat_ipc_ep *ep) nni_posix_epdesc_close((void *) ep); } +int +nni_plat_ipc_ep_set_permissions(nni_plat_ipc_ep *ep, uint32_t bits) +{ + return (nni_posix_epdesc_set_permissions((void *) ep, (mode_t) bits)); +} + +int +nni_plat_ipc_ep_set_security_descriptor(nni_plat_ipc_ep *ep, void *attr) +{ + NNI_ARG_UNUSED(ep); + NNI_ARG_UNUSED(attr); + return (NNG_ENOTSUP); +} + // UNIX DOMAIN SOCKETS -- these have names in the file namespace. // We are going to check to see if there was a name already there. // If there was, and nothing is listening (ECONNREFUSED), then we diff --git a/src/platform/posix/posix_tcp.c b/src/platform/posix/posix_tcp.c index 81ec330b..c00f9433 100644 --- a/src/platform/posix/posix_tcp.c +++ b/src/platform/posix/posix_tcp.c @@ -36,7 +36,7 @@ nni_plat_tcp_ep_init(nni_plat_tcp_ep **epp, const nni_sockaddr *lsa, NNI_ARG_UNUSED(mode); - if ((rv = nni_posix_epdesc_init(&ed)) != 0) { + if ((rv = nni_posix_epdesc_init(&ed, mode)) != 0) { return (rv); } diff --git a/src/platform/windows/win_ipc.c b/src/platform/windows/win_ipc.c index d372f639..7d118672 100644 --- a/src/platform/windows/win_ipc.c +++ b/src/platform/windows/win_ipc.c @@ -21,14 +21,15 @@ struct nni_plat_ipc_pipe { }; struct nni_plat_ipc_ep { - char path[NNG_MAXADDRLEN + 16]; - nni_sockaddr addr; - int mode; - int started; - HANDLE p; // accept side only - nni_win_event acc_ev; // accept side only - nni_aio * con_aio; // conn side only - nni_list_node node; // conn side uses this + char path[NNG_MAXADDRLEN + 16]; + nni_sockaddr addr; + int mode; + bool started; + HANDLE p; // accept side only + nni_win_event acc_ev; // accept side only + nni_aio * con_aio; // conn side only + nni_list_node node; // conn side uses this + SECURITY_ATTRIBUTES sec_attr; }; static int nni_win_ipc_pipe_start(nni_win_event *, nni_aio *); @@ -203,7 +204,10 @@ nni_plat_ipc_ep_init(nni_plat_ipc_ep **epp, const nni_sockaddr *sa, int mode) } ZeroMemory(ep, sizeof(*ep)); - ep->mode = mode; + ep->mode = mode; + ep->sec_attr.nLength = sizeof(ep->sec_attr); + ep->sec_attr.lpSecurityDescriptor = NULL; + ep->sec_attr.bInheritHandle = FALSE; NNI_LIST_NODE_INIT(&ep->node); ep->addr = *sa; @@ -214,6 +218,30 @@ nni_plat_ipc_ep_init(nni_plat_ipc_ep **epp, const nni_sockaddr *sa, int mode) } int +nni_plat_ipc_ep_set_permissions(nni_plat_ipc_ep *ep, uint32_t bits) +{ + NNI_ARG_UNUSED(ep); + NNI_ARG_UNUSED(bits); + return (NNG_ENOTSUP); +} + +int +nni_plat_ipc_ep_set_security_descriptor(nni_plat_ipc_ep *ep, void *desc) +{ + if (ep->started) { + return (NNG_EBUSY); + } + if (ep->mode != NNI_EP_MODE_LISTEN) { + return (NNG_ENOTSUP); + } + if (!IsValidSecurityDescriptor((SECURITY_DESCRIPTOR *) desc)) { + return (NNG_EINVAL); + } + ep->sec_attr.lpSecurityDescriptor = desc; + return (0); +} + +int nni_plat_ipc_ep_listen(nni_plat_ipc_ep *ep) { int rv; @@ -233,7 +261,7 @@ nni_plat_ipc_ep_listen(nni_plat_ipc_ep *ep) FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, - PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, NULL); + PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &ep->sec_attr); if (p == INVALID_HANDLE_VALUE) { if ((rv = GetLastError()) == ERROR_ACCESS_DENIED) { rv = NNG_EADDRINUSE; @@ -252,7 +280,7 @@ nni_plat_ipc_ep_listen(nni_plat_ipc_ep *ep) } ep->p = p; - ep->started = 1; + ep->started = true; return (0); failed: @@ -281,7 +309,7 @@ nni_win_ipc_acc_finish(nni_win_event *evt, nni_aio *aio) PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, - PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, NULL); + PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &ep->sec_attr); if (newp == INVALID_HANDLE_VALUE) { rv = nni_win_error(GetLastError()); // We connected, but as we cannot get a new pipe, diff --git a/src/transport/ipc/ipc.c b/src/transport/ipc/ipc.c index 61b89f20..3dbccb50 100644 --- a/src/transport/ipc/ipc.c +++ b/src/transport/ipc/ipc.c @@ -13,6 +13,7 @@ #include <string.h> #include "core/nng_impl.h" +#include "ipc.h" // IPC transport. Platform specific IPC operations must be // supplied as well. Normally the IPC is UNIX domain sockets or @@ -739,6 +740,40 @@ nni_ipc_ep_get_addr(void *arg, void *data, size_t *szp, int typ) return (nni_copyout_sockaddr(&ep->sa, data, szp, typ)); } +static int +nni_ipc_ep_setopt_permissions(void *arg, const void *data, size_t sz, int typ) +{ + nni_ipc_ep *ep = arg; + int val; + int rv; + + // Probably we could further limit this -- most systems don't have + // meaningful chmod beyond the lower 9 bits. + rv = nni_copyin_int(&val, data, sz, 0, 0x7FFFFFFF, typ); + if ((rv == 0) && (ep != NULL)) { + rv = nni_plat_ipc_ep_set_permissions(ep->iep, val); + } + return (rv); +} + +static int +nni_ipc_ep_setopt_security_desc( + void *arg, const void *data, size_t sz, int typ) +{ + nni_ipc_ep *ep = arg; + void * ptr; + int rv; + + if ((rv = nni_copyin_ptr((void **) &ptr, data, sz, typ)) != 0) { + return (rv); + } + + if (ep == NULL) { + return (0); + } + return (nni_plat_ipc_ep_set_security_descriptor(ep->iep, ptr)); +} + static nni_tran_pipe_option nni_ipc_pipe_options[] = { { .po_name = NNG_OPT_REMADDR, @@ -779,6 +814,18 @@ static nni_tran_ep_option nni_ipc_ep_options[] = { .eo_getopt = nni_ipc_ep_get_addr, .eo_setopt = NULL, }, + { + .eo_name = NNG_OPT_IPC_SECURITY_DESCRIPTOR, + .eo_type = NNI_TYPE_POINTER, + .eo_getopt = NULL, + .eo_setopt = nni_ipc_ep_setopt_security_desc, + }, + { + .eo_name = NNG_OPT_IPC_PERMISSIONS, + .eo_type = NNI_TYPE_INT32, + .eo_getopt = NULL, + .eo_setopt = nni_ipc_ep_setopt_permissions, + }, // terminate list { .eo_name = NULL, diff --git a/src/transport/ipc/ipc.h b/src/transport/ipc/ipc.h index 4c4c5708..42cbdb08 100644 --- a/src/transport/ipc/ipc.h +++ b/src/transport/ipc/ipc.h @@ -1,6 +1,6 @@ // -// Copyright 2017 Garrett D'Amore <garrett@damore.org> -// Copyright 2017 Capitar IT Group BV <info@capitar.com> +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> // // This software is supplied under the terms of the MIT License, a // copy of which should be located in the distribution where this @@ -16,4 +16,16 @@ NNG_DECL int nng_ipc_register(void); +// Security Descriptor. This option may only be set on listeners +// on the Windows platform, where the object is a pointer to a +// a Windows SECURITY_DESCRIPTOR. +#define NNG_OPT_IPC_SECURITY_DESCRIPTOR "ipc:security-descriptor" + +// Permissions bits. This option is only valid for listeners on +// POSIX platforms and others that honor UNIX style permission bits. +// Note that some platforms may not honor the permissions here, although +// at least Linux and macOS seem to do so. Check before you rely on +// this for security. +#define NNG_OPT_IPC_PERMISSIONS "ipc:permissions" + #endif // NNG_TRANSPORT_IPC_IPC_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6db01e82..3cfc89fa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -136,6 +136,8 @@ add_nng_test(httpserver 30 NNG_SUPP_HTTP) add_nng_test(idhash 5 ON) add_nng_test(inproc 5 NNG_TRANSPORT_INPROC) add_nng_test(ipc 5 NNG_TRANSPORT_IPC) +add_nng_test(ipcperms 5 NNG_TRANSPORT_IPC) +add_nng_test(ipcwinsec 5 NNG_TRANSPORT_IPC) add_nng_test(list 5 ON) add_nng_test(message 5 ON) add_nng_test(multistress 60 ON) diff --git a/tests/ipcperms.c b/tests/ipcperms.c new file mode 100644 index 00000000..84801efd --- /dev/null +++ b/tests/ipcperms.c @@ -0,0 +1,114 @@ +// +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include "convey.h" +#include "trantest.h" + +#include "nng.h" + +#include "protocol/reqrep0/rep.h" +#include "protocol/reqrep0/req.h" +#include "transport/ipc/ipc.h" + +#include "stubs.h" + +#ifndef _WIN32 +#include <errno.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> +#include <unistd.h> +#endif + +#define ADDR "/tmp/ipc_perms_test" + +// Inproc tests. + +#ifdef _WIN32 +TestMain("IPC Permissions", { + + atexit(nng_fini); + Convey("Given a socket and an IPC listener", { + nng_socket s; + nng_listener l; + + So(nng_rep0_open(&s) == 0); + Reset({ nng_close(s); }); + So(nng_listener_create(&l, s, "ipc://" ADDR) == 0); + Convey("We cannot set perms on Windows", { + So(nng_listener_setopt_int(l, NNG_OPT_IPC_PERMISSIONS, + 0444) == NNG_ENOTSUP); + }); + }); +}) +#else +TestMain("IPC Permissions", { + atexit(nng_fini); + + Convey("Given a socket and an IPC listener", { + nng_socket s; + nng_listener l; + + So(nng_rep0_open(&s) == 0); + Reset({ + nng_close(s); + unlink(ADDR); + }); + So(nng_listener_create(&l, s, "ipc://" ADDR) == 0); + Convey("We can set perms on POSIX", { + struct stat st; + So(nng_listener_setopt_int( + l, NNG_OPT_IPC_PERMISSIONS, 0444) == 0); + So(nng_listener_start(l, 0) == 0); + So(stat(ADDR, &st) == 0); + So((st.st_mode & 0777) == 0444); + + Convey("And permissions are honored", { + struct sockaddr_un sa; + int cfd; + + if (geteuid() == 0) { + Skip("Running as root"); + } + strcpy(sa.sun_path, ADDR); + sa.sun_family = AF_UNIX; + So((cfd = socket(AF_UNIX, SOCK_STREAM, 0)) >= + 0); + Reset({ close(cfd); }); + So(connect(cfd, (void *) &sa, sizeof(sa)) < 0); + So(errno == EACCES); + }); + }); + + Convey("We cannot set perms after it is started", { + So(nng_listener_start(l, 0) == 0); + So(nng_listener_setopt_int( + l, NNG_OPT_IPC_PERMISSIONS, 0444) == NNG_EBUSY); + }); + + Convey("We cannot set bogus permissions", { + So(nng_listener_setopt_int(l, NNG_OPT_IPC_PERMISSIONS, + S_IFREG) == NNG_EINVAL); + }); + }); + + Convey("We cannot set perms on an IPC dialer", { + nng_socket s; + nng_dialer d; + + So(nng_rep0_open(&s) == 0); + Reset({ nng_close(s); }); + So(nng_dialer_create(&d, s, "ipc://" ADDR) == 0); + So(nng_dialer_setopt_int(d, NNG_OPT_IPC_PERMISSIONS, 0444) == + NNG_ENOTSUP); + }); +}) +#endif
\ No newline at end of file diff --git a/tests/ipcwinsec.c b/tests/ipcwinsec.c new file mode 100644 index 00000000..80ec163c --- /dev/null +++ b/tests/ipcwinsec.c @@ -0,0 +1,200 @@ +// +// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> +// Copyright 2018 Capitar IT Group BV <info@capitar.com> +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include "convey.h" +#include "trantest.h" + +#include "nng.h" + +#include "protocol/reqrep0/rep.h" +#include "protocol/reqrep0/req.h" +#include "transport/ipc/ipc.h" + +#include "stubs.h" + +#define ADDR "/tmp/ipc_winsec_test" + +// Inproc tests. + +#ifndef _WIN32 +TestMain("IPC Security Descriptor", { + + atexit(nng_fini); + Convey("Given a socket and an IPC listener", { + nng_socket s; + nng_listener l; + int x; + + So(nng_rep0_open(&s) == 0); + Reset({ nng_close(s); }); + So(nng_listener_create(&l, s, "ipc://" ADDR) == 0); + Convey("We cannot set Windows SECURITY_DESCRIPTOR on POSIX", { + So(nng_listener_setopt_ptr(l, + NNG_OPT_IPC_SECURITY_DESCRIPTOR, + &x) == NNG_ENOTSUP); + }); + }); +}) +#else + +#include <assert.h> + +// Microsoft prefers CamelCase header names, but relies on case insensitive +// file systems to make that work. The rest of the world (min-gw64 included) +// uses case sensitive names and lowercase. + +#include <accctrl.h> + +#include <sddl.h> + +#include <aclapi.h> + +SECURITY_DESCRIPTOR * +sdescAuthUsers(PSID sid, PACL *aclp) +{ + SECURITY_DESCRIPTOR *sdesc; + EXPLICIT_ACCESS xa; + ACL * acl; + + sdesc = calloc(SECURITY_DESCRIPTOR_MIN_LENGTH, 1); + assert(sdesc != NULL); + + InitializeSecurityDescriptor(sdesc, SECURITY_DESCRIPTOR_REVISION); + + xa.grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; + xa.grfAccessMode = SET_ACCESS; + xa.grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; + xa.Trustee.TrusteeForm = TRUSTEE_IS_SID; + xa.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + xa.Trustee.ptstrName = (LPSTR) sid; + + SetEntriesInAcl(1, &xa, NULL, &acl); + *aclp = acl; + + SetSecurityDescriptorDacl(sdesc, TRUE, acl, FALSE); + return (sdesc); +} + +TestMain("IPC Security Descriptor", { + atexit(nng_fini); + + Convey("Given a socket and an IPC listener", { + nng_socket s; + nng_listener l; + + So(nng_rep0_open(&s) == 0); + Reset({ nng_close(s); }); + + So(nng_listener_create(&l, s, "ipc://" ADDR) == 0); + Convey("We can set security descriptor on Windows", { + + SECURITY_DESCRIPTOR *sdesc; + SID users; + DWORD size; + PACL acl = NULL; + + size = sizeof(users); + CreateWellKnownSid( + WinAuthenticatedUserSid, NULL, &users, &size); + + sdesc = sdescAuthUsers(&users, &acl); + assert(sdesc != NULL); + assert(acl != NULL); + Reset({ + free(sdesc); + LocalFree(acl); + }); + + So(nng_listener_setopt_ptr(l, + NNG_OPT_IPC_SECURITY_DESCRIPTOR, sdesc) == 0); + So(nng_listener_start(l, 0) == 0); + + Convey("And they are effective", { + PACL dacl; + PSECURITY_DESCRIPTOR sd; + PACE_HEADER ace; + PSID asid; + PACCESS_ALLOWED_ACE allowed; + + HANDLE ph = CreateFileA("\\\\.\\\\pipe\\" ADDR, + READ_CONTROL, 0, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, NULL); + + So(ph != INVALID_HANDLE_VALUE); + Reset({ CloseHandle(ph); }); + + So(GetSecurityInfo(ph, SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, NULL, NULL, + &dacl, NULL, &sd) == ERROR_SUCCESS); + Reset({ LocalFree(sd); }); + + So(dacl->AceCount == 1); + So(GetAce(dacl, 0, &ace) == TRUE); + allowed = (PACCESS_ALLOWED_ACE) ace; + asid = (PSID) &allowed->SidStart; + So(IsValidSid(asid)); + So(EqualSid(asid, &users) == TRUE); + }); + }); + + Convey("We cannot set security descriptor after started", { + + SECURITY_DESCRIPTOR *sdesc; + SID users; + DWORD size; + PACL acl = NULL; + + size = sizeof(users); + CreateWellKnownSid( + WinAuthenticatedUserSid, NULL, &users, &size); + + sdesc = sdescAuthUsers(&users, &acl); + assert(sdesc != NULL); + assert(acl != NULL); + Reset({ + free(sdesc); + LocalFree(acl); + }); + + So(nng_listener_start(l, 0) == 0); + So(nng_listener_setopt_ptr(l, + NNG_OPT_IPC_SECURITY_DESCRIPTOR, + sdesc) == NNG_EBUSY); + }); + + Convey("We cannot set bogus security", { + So(nng_listener_setopt_ptr(l, + NNG_OPT_IPC_SECURITY_DESCRIPTOR, + NULL) == NNG_EINVAL); + }); + }); + + Convey("We cannot set security descriptor on an IPC dialer", { + nng_socket s; + nng_dialer d; + SECURITY_DESCRIPTOR *sdesc; + + sdesc = calloc(SECURITY_DESCRIPTOR_MIN_LENGTH, 1); + assert(sdesc != NULL); + InitializeSecurityDescriptor( + sdesc, SECURITY_DESCRIPTOR_REVISION); + + So(nng_rep0_open(&s) == 0); + Reset({ + nng_close(s); + free(sdesc); + }); + + So(nng_dialer_create(&d, s, "ipc://" ADDR) == 0); + So(nng_dialer_setopt_ptr(d, NNG_OPT_IPC_SECURITY_DESCRIPTOR, + sdesc) == NNG_ENOTSUP); + }); +}) +#endif
\ No newline at end of file |
