diff options
| author | Garrett D'Amore <garrett@damore.org> | 2018-04-27 14:14:08 -0700 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2018-04-30 11:33:10 -0700 |
| commit | 2b0d31553e542c130e2595ff9a3ac9756a2c1619 (patch) | |
| tree | f9ef54cfe7c4336e4765091445aa4d86a53645b5 | |
| parent | 88c7a328dfaca4a9fce13ebbc4bce6b24d048c3e (diff) | |
| download | nng-2b0d31553e542c130e2595ff9a3ac9756a2c1619.tar.gz nng-2b0d31553e542c130e2595ff9a3ac9756a2c1619.tar.bz2 nng-2b0d31553e542c130e2595ff9a3ac9756a2c1619.zip | |
fixes #6 Security attributes support
fixes #382 Permissions support for IPC on POSIX
This adds support for permission management on Windows and
POSIX systems. There are two different properties, and they
are very different.
Tests and documentation are included.
| -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 |
