diff options
Diffstat (limited to 'lib/libcasper')
79 files changed, 18986 insertions, 0 deletions
diff --git a/lib/libcasper/Makefile b/lib/libcasper/Makefile new file mode 100644 index 000000000000..0a1074d4613d --- /dev/null +++ b/lib/libcasper/Makefile @@ -0,0 +1,10 @@ +.include <src.opts.mk> + +SUBDIR= libcasper +SUBDIR+= services + +SUBDIR.${MK_TESTS}+= tests + +SUBDIR_PARALLEL= + +.include <bsd.subdir.mk> diff --git a/lib/libcasper/Makefile.inc b/lib/libcasper/Makefile.inc new file mode 100644 index 000000000000..00bd221feb27 --- /dev/null +++ b/lib/libcasper/Makefile.inc @@ -0,0 +1,7 @@ +.include <src.opts.mk> + +.if ${MK_CASPER} != "no" +CFLAGS+=-DWITH_CASPER +.endif + +.include "../Makefile.inc" diff --git a/lib/libcasper/libcasper/Makefile b/lib/libcasper/libcasper/Makefile new file mode 100644 index 000000000000..4db26f665f19 --- /dev/null +++ b/lib/libcasper/libcasper/Makefile @@ -0,0 +1,43 @@ +PACKAGE= runtime + +SHLIBDIR?= /lib + +.include <src.opts.mk> + +.if ${MK_CASPER} != "no" +SHLIB= casper +SHLIB_MAJOR= 1 + +SRCS= libcasper.c +SRCS+= libcasper_impl.c +SRCS+= libcasper_service.c +SRCS+= service.c +SRCS+= zygote.c +.endif + +INCS= libcasper.h +INCS+= libcasper_service.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +MAN+= libcasper.3 +MAN+= libcasper_service.3 + +MLINKS+=libcasper.3 cap_init.3 +MLINKS+=libcasper.3 cap_wrap.3 +MLINKS+=libcasper.3 cap_unwrap.3 +MLINKS+=libcasper.3 cap_sock.3 +MLINKS+=libcasper.3 cap_clone.3 +MLINKS+=libcasper.3 cap_close.3 +MLINKS+=libcasper.3 cap_limit_get.3 +MLINKS+=libcasper.3 cap_limit_set.3 +MLINKS+=libcasper.3 cap_send_nvlist.3 +MLINKS+=libcasper.3 cap_recv_nvlist.3 +MLINKS+=libcasper.3 cap_xfer_nvlist.3 +MLINKS+=libcasper.3 cap_service_open.3 + +MLINKS+=libcasper_service.3 CREATE_SERVICE.3 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/libcasper/Makefile.depend b/lib/libcasper/libcasper/Makefile.depend new file mode 100644 index 000000000000..d223a871a521 --- /dev/null +++ b/lib/libcasper/libcasper/Makefile.depend @@ -0,0 +1,16 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/libcasper/libcasper.3 b/lib/libcasper/libcasper/libcasper.3 new file mode 100644 index 000000000000..15f231d7e366 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper.3 @@ -0,0 +1,317 @@ +.\" Copyright (c) 2013 The FreeBSD Foundation +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" This documentation was written by Pawel Jakub Dawidek under sponsorship +.\" from the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt LIBCASPER 3 +.Os +.Sh NAME +.Nm cap_init , +.Nm cap_wrap , +.Nm cap_unwrap , +.Nm cap_sock , +.Nm cap_clone , +.Nm cap_close , +.Nm cap_limit_get , +.Nm cap_limit_set , +.Nm cap_send_nvlist , +.Nm cap_recv_nvlist , +.Nm cap_xfer_nvlist , +.Nm cap_service_open +.Nd "library for handling application capabilities" +.Sh LIBRARY +.Lb libcasper +.Sh SYNOPSIS +.Fd #define WITH_CASPER +.In sys/nv.h +.In libcasper.h +.Ft "cap_channel_t *" +.Fn cap_init "void" +.Ft "cap_channel_t *" +.Fn cap_wrap "int sock" "int flags" +.Ft "int" +.Fn cap_unwrap "cap_channel_t *chan" "int *flags" +.Ft "int" +.Fn cap_sock "const cap_channel_t *chan" +.Ft "cap_channel_t *" +.Fn cap_clone "const cap_channel_t *chan" +.Ft "void" +.Fn cap_close "cap_channel_t *chan" +.Ft "int" +.Fn cap_limit_get "const cap_channel_t *chan" "nvlist_t **limitsp" +.Ft "int" +.Fn cap_limit_set "const cap_channel_t *chan" "nvlist_t *limits" +.Ft "int" +.Fn cap_send_nvlist "const cap_channel_t *chan" "const nvlist_t *nvl" +.Ft "nvlist_t *" +.Fn cap_recv_nvlist "const cap_channel_t *chan" +.Ft "nvlist_t *" +.Fn cap_xfer_nvlist "const cap_channel_t *chan" "nvlist_t *nvl" +.Ft "cap_channel_t *" +.Fn cap_service_open "const cap_channel_t *chan" "const char *name" +.Sh DESCRIPTION +The +.Nm libcasper +library provides for the control of application capabilities through +the casper process. +.Pp +An application capability, represented by the +.Vt cap_channel_t +type, is a communication channel between the caller and the casper +daemon or an instance of one of the daemon's services. +A capability to the casper process, obtained with the +.Fn cap_init +function, allows a program to create capabilities to access +the casper daemon's services via the +.Fn cap_service_open +function. +.Pp +The +.Fn cap_init +function instantiates a capability to allow a program to access +the casper daemon. +.Pp +The +.Fn cap_wrap +function creates a +.Vt cap_channel_t +based on the socket supplied in the call. +The function is used when a capability is inherited through the +.Xr execve 2 +system call, +or sent over a +.Xr unix 4 +domain socket as a file descriptor, +and has to be converted into a +.Vt cap_channel_t . +The +.Fa flags +argument defines the channel behavior. +The supported flags are: +.Bl -ohang -offset indent +.It CASPER_NO_UNIQ +The communication between the process and the casper daemon uses no +unique version of nvlist. +.El +.Pp +The +.Fn cap_unwrap +function returns the +.Xr unix 4 +domain socket used by the daemon service, +and frees the +.Vt cap_channel_t +structure. +.Pp +The +.Fn cap_clone +function returns a clone of the capability passed as its only argument. +.Pp +The +.Fn cap_close +function closes, and frees, the given capability. +.Pp +The +.Fn cap_sock +function returns the +.Xr unix 4 +domain socket descriptor associated with the given capability for use with +system calls such as: +.Xr kevent 2 , +.Xr poll 2 , +and +.Xr select 2 . +.Pp +The +.Fn cap_limit_get +function stores the current limits of the given capability in the +.Fa limitsp +argument. +If the function returns +.Va 0 +and +.Dv NULL +is stored in the +.Fa limitsp +argument, +there are no limits set. +.Pp +The +.Fn cap_limit_set +function sets limits for the given capability. +The limits are provided as an +.Xr nvlist 9 . +The exact format of the limits depends on the service that the +capability represents. +.Fn cap_limit_set +frees the limits passed to the call, +whether or not the operation succeeds or fails. +.Pp +The +.Fn cap_send_nvlist +function sends the given +.Xr nvlist 9 +over the given capability. +This is a low level interface to communicate with casper services. +It is expected that most services will provide a higher level API. +.Pp +The +.Fn cap_recv_nvlist +function receives the given +.Xr nvlist 9 +over the given capability. +.Pp +The +.Fn cap_xfer_nvlist +function sends the given +.Xr nvlist 9 , +destroys it, +and receives a new +.Xr nvlist 9 +in response over the given capability. +It does not matter if the function succeeds or fails, the +.Xr nvlist 9 +given for sending will always be destroyed before the function returns. +.Pp +The +.Fn cap_service_open +function opens the casper service named in the call using +the casper capability obtained via the +.Fn cap_init +function. +The +.Fn cap_service_open +function returns a capability that provides access to the opened service. +Casper supports the following services in the base system: +.Pp +.Bl -tag -width "system.random" -compact -offset indent +.It system.dns +provides libc compatible DNS API +.It system.fileargs +provides an API for opening files specified on a command line +.It system.grp +provides a +.Xr getgrent 3 +compatible API +.It system.net +provides a libc compatible network API +.It system.netdb +provides libc compatible network proto API +.It system.pwd +provides a +.Xr getpwent 3 +compatible API +.It system.sysctl +provides a +.Xr sysctlbyname 3 +compatible API +.It system.syslog +provides a +.Xr syslog 3 +compatible API +.El +.Pp +.Fn cap_init +must be called from a single-threaded context. +.Fn cap_clone , +.Fn cap_close , +.Fn cap_limit_get , +.Fn cap_limit_set , +.Fn cap_send_nvlist , +.Fn cap_recv_nvlist , +and +.Fn cap_service_open +are reentrant but not thread-safe. +That is, they may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh RETURN VALUES +The +.Fn cap_clone , +.Fn cap_init , +.Fn cap_recv_nvlist , +.Fn cap_service_open , +.Fn cap_wrap +and +.Fn cap_xfer_nvlist +functions return +.Dv NULL +and set the +.Va errno +variable on failure. +.Pp +The +.Fn cap_limit_get , +.Fn cap_limit_set +and +.Fn cap_send_nvlist +functions return +.Dv -1 +and set the +.Va errno +variable on failure. +.Pp +The +.Fn cap_close , +.Fn cap_sock +and +.Fn cap_unwrap +functions always succeed. +.Sh SEE ALSO +.Xr errno 2 , +.Xr execve 2 , +.Xr kevent 2 , +.Xr poll 2 , +.Xr select 2 , +.Xr cap_dns 3 , +.Xr cap_fileargs 3 , +.Xr cap_grp 3 , +.Xr cap_net 3 , +.Xr cap_netdb 3 , +.Xr cap_pwd 3 , +.Xr cap_sysctl 3 , +.Xr cap_syslog 3 , +.Xr libcasper_service 3 , +.Xr capsicum 4 , +.Xr unix 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm libcasper +library first appeared in +.Fx 10.3 . +.Sh AUTHORS +The +.Nm libcasper +library was implemented by +.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net +under sponsorship from the FreeBSD Foundation. +The +.Nm libcasper +new architecture was implemented by +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org +. diff --git a/lib/libcasper/libcasper/libcasper.c b/lib/libcasper/libcasper/libcasper.c new file mode 100644 index 000000000000..9cc008cdb7bc --- /dev/null +++ b/lib/libcasper/libcasper/libcasper.c @@ -0,0 +1,357 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012-2013 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/nv.h> +#include <sys/procdesc.h> + +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libcasper.h" +#include "libcasper_impl.h" + +#define CASPER_VALID_FLAGS (CASPER_NO_UNIQ) + +/* + * Structure describing communication channel between two separated processes. + */ +#define CAP_CHANNEL_MAGIC 0xcac8a31 +struct cap_channel { + /* + * Magic value helps to ensure that a pointer to the right structure is + * passed to our functions. + */ + int cch_magic; + /* Socket descriptor for IPC. */ + int cch_sock; + /* Process descriptor for casper. */ + int cch_pd; + /* Flags to communicate with casper. */ + int cch_flags; +}; + +static bool +cap_add_pd(cap_channel_t *chan, int pd) +{ + + if (!fd_is_valid(pd)) + return (false); + chan->cch_pd = pd; + return (true); +} + +int +cap_channel_flags(const cap_channel_t *chan) +{ + + return (chan->cch_flags); +} + +cap_channel_t * +cap_init(void) +{ + pid_t pid; + int sock[2], serrno, pfd; + bool ret; + cap_channel_t *chan; + + if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, + sock) == -1) { + return (NULL); + } + + pid = pdfork(&pfd, 0); + if (pid == 0) { + /* Child. */ + close(sock[0]); + casper_main_loop(sock[1]); + /* NOTREACHED. */ + } else if (pid > 0) { + /* Parent. */ + close(sock[1]); + chan = cap_wrap(sock[0], 0); + if (chan == NULL) { + serrno = errno; + close(sock[0]); + close(pfd); + errno = serrno; + return (NULL); + } + ret = cap_add_pd(chan, pfd); + assert(ret); + return (chan); + } + + /* Error. */ + serrno = errno; + close(sock[0]); + close(sock[1]); + errno = serrno; + return (NULL); +} + +cap_channel_t * +cap_wrap(int sock, int flags) +{ + cap_channel_t *chan; + + if (!fd_is_valid(sock)) + return (NULL); + + if ((flags & CASPER_VALID_FLAGS) != flags) + return (NULL); + + chan = malloc(sizeof(*chan)); + if (chan != NULL) { + chan->cch_sock = sock; + chan->cch_pd = -1; + chan->cch_flags = flags; + chan->cch_magic = CAP_CHANNEL_MAGIC; + } + + return (chan); +} + +int +cap_unwrap(cap_channel_t *chan, int *flags) +{ + int sock; + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + sock = chan->cch_sock; + if (chan->cch_pd != -1) + close(chan->cch_pd); + if (flags != NULL) + *flags = chan->cch_flags; + chan->cch_magic = 0; + free(chan); + + return (sock); +} + +cap_channel_t * +cap_clone(const cap_channel_t *chan) +{ + cap_channel_t *newchan; + nvlist_t *nvl; + int newsock; + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + nvl = nvlist_create(channel_nvlist_flags(chan)); + nvlist_add_string(nvl, "cmd", "clone"); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (NULL); + if (nvlist_get_number(nvl, "error") != 0) { + errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (NULL); + } + newsock = nvlist_take_descriptor(nvl, "sock"); + nvlist_destroy(nvl); + newchan = cap_wrap(newsock, chan->cch_flags); + if (newchan == NULL) { + int serrno; + + serrno = errno; + close(newsock); + errno = serrno; + } + + return (newchan); +} + +void +cap_close(cap_channel_t *chan) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + chan->cch_magic = 0; + if (chan->cch_pd != -1) + close(chan->cch_pd); + close(chan->cch_sock); + free(chan); +} + +int +cap_sock(const cap_channel_t *chan) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + return (chan->cch_sock); +} + +int +cap_limit_set(const cap_channel_t *chan, nvlist_t *limits) +{ + nvlist_t *nvlmsg; + int error; + + nvlmsg = nvlist_create(channel_nvlist_flags(chan)); + nvlist_add_string(nvlmsg, "cmd", "limit_set"); + nvlist_add_nvlist(nvlmsg, "limits", limits); + nvlmsg = cap_xfer_nvlist(chan, nvlmsg); + if (nvlmsg == NULL) { + nvlist_destroy(limits); + return (-1); + } + error = (int)nvlist_get_number(nvlmsg, "error"); + nvlist_destroy(nvlmsg); + nvlist_destroy(limits); + if (error != 0) { + errno = error; + return (-1); + } + return (0); +} + +int +cap_limit_get(const cap_channel_t *chan, nvlist_t **limitsp) +{ + nvlist_t *nvlmsg; + int error; + + nvlmsg = nvlist_create(channel_nvlist_flags(chan)); + nvlist_add_string(nvlmsg, "cmd", "limit_get"); + nvlmsg = cap_xfer_nvlist(chan, nvlmsg); + if (nvlmsg == NULL) + return (-1); + error = (int)nvlist_get_number(nvlmsg, "error"); + if (error != 0) { + nvlist_destroy(nvlmsg); + errno = error; + return (-1); + } + if (nvlist_exists_null(nvlmsg, "limits")) + *limitsp = NULL; + else + *limitsp = nvlist_take_nvlist(nvlmsg, "limits"); + nvlist_destroy(nvlmsg); + return (0); +} + +int +cap_send_nvlist(const cap_channel_t *chan, const nvlist_t *nvl) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + return (nvlist_send(chan->cch_sock, nvl)); +} + +nvlist_t * +cap_recv_nvlist(const cap_channel_t *chan) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + return (nvlist_recv(chan->cch_sock, + channel_nvlist_flags(chan))); +} + +nvlist_t * +cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl) +{ + + assert(chan != NULL); + assert(chan->cch_magic == CAP_CHANNEL_MAGIC); + + return (nvlist_xfer(chan->cch_sock, nvl, + channel_nvlist_flags(chan))); +} + +cap_channel_t * +cap_service_open(const cap_channel_t *chan, const char *name) +{ + cap_channel_t *newchan; + nvlist_t *nvl; + int sock, error; + int flags; + + sock = -1; + + nvl = nvlist_create(channel_nvlist_flags(chan)); + nvlist_add_string(nvl, "cmd", "open"); + nvlist_add_string(nvl, "service", name); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (NULL); + error = (int)nvlist_get_number(nvl, "error"); + if (error != 0) { + nvlist_destroy(nvl); + errno = error; + return (NULL); + } + sock = nvlist_take_descriptor(nvl, "chanfd"); + flags = nvlist_take_number(nvl, "chanflags"); + assert(sock >= 0); + nvlist_destroy(nvl); + nvl = NULL; + newchan = cap_wrap(sock, flags); + if (newchan == NULL) + goto fail; + return (newchan); +fail: + error = errno; + close(sock); + errno = error; + return (NULL); +} + +int +cap_service_limit(const cap_channel_t *chan, const char * const *names, + size_t nnames) +{ + nvlist_t *limits; + unsigned int i; + + limits = nvlist_create(channel_nvlist_flags(chan)); + for (i = 0; i < nnames; i++) + nvlist_add_null(limits, names[i]); + return (cap_limit_set(chan, limits)); +} diff --git a/lib/libcasper/libcasper/libcasper.h b/lib/libcasper/libcasper/libcasper.h new file mode 100644 index 000000000000..403ddb73ad73 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper.h @@ -0,0 +1,303 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012-2013 The FreeBSD Foundation + * Copyright (c) 2015-2017 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LIBCASPER_H_ +#define _LIBCASPER_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/types.h> +#include <sys/nv.h> + +#include <stdlib.h> +#include <unistd.h> + +#define CASPER_NO_UNIQ 0x00000001 + +#ifndef _NVLIST_T_DECLARED +#define _NVLIST_T_DECLARED +struct nvlist; + +typedef struct nvlist nvlist_t; +#endif + +#ifndef _CAP_CHANNEL_T_DECLARED +#define _CAP_CHANNEL_T_DECLARED +#ifdef WITH_CASPER +struct cap_channel; + +typedef struct cap_channel cap_channel_t; +#define CASPER_SUPPORT (1) +#else +struct cap_channel { + int cch_fd; + int cch_flags; +}; +typedef struct cap_channel cap_channel_t; +#define CASPER_SUPPORT (0) +#endif /* ! WITH_CASPER */ +#endif /* ! _CAP_CHANNEL_T_DECLARED */ + +__BEGIN_DECLS + +#ifdef WITH_CASPER +int cap_channel_flags(const cap_channel_t *chan); +#else +static inline int +cap_channel_flags(const cap_channel_t *chan) +{ + + return (chan->cch_flags); +} +#endif + +static inline int +channel_nvlist_flags(const cap_channel_t *chan) +{ + int flags; + + flags = 0; + if ((cap_channel_flags(chan) & CASPER_NO_UNIQ) != 0) + flags |= NV_FLAG_NO_UNIQUE; + + return (flags); +} + +/* + * The functions opens unrestricted communication channel to Casper. + */ +#ifdef WITH_CASPER +cap_channel_t *cap_init(void); +#else +static inline cap_channel_t * +cap_init(void) +{ + cap_channel_t *chan; + + chan = (cap_channel_t *)malloc(sizeof(*chan)); + if (chan != NULL) { + chan->cch_fd = -1; + } + return (chan); +} +#endif + +/* + * The functions to communicate with service. + */ +#ifdef WITH_CASPER +cap_channel_t *cap_service_open(const cap_channel_t *chan, const char *name); +int cap_service_limit(const cap_channel_t *chan, + const char * const *names, size_t nnames); +#else +static inline cap_channel_t * +cap_service_open(const cap_channel_t *chan __unused, + const char *name __unused) +{ + + return (cap_init()); +} + +static inline int +cap_service_limit(const cap_channel_t *chan __unused, + const char * const *names __unused, size_t nnames __unused) +{ + + return (0); +} +#endif + +/* + * The function creates cap_channel_t based on the given socket. + */ +#ifdef WITH_CASPER +cap_channel_t *cap_wrap(int sock, int flags); +#else +static inline cap_channel_t * +cap_wrap(int sock, int flags) +{ + cap_channel_t *chan; + + chan = cap_init(); + if (chan != NULL) { + chan->cch_fd = sock; + chan->cch_flags = flags; + } + return (chan); +} +#endif + +/* + * The function returns communication socket and frees cap_channel_t. + */ +#ifdef WITH_CASPER +int cap_unwrap(cap_channel_t *chan, int *flags); +#else +static inline int +cap_unwrap(cap_channel_t *chan) +{ + int fd; + + fd = chan->cch_fd; + free(chan); + return (fd); +} +#endif + +/* + * The function clones the given capability. + */ +#ifdef WITH_CASPER +cap_channel_t *cap_clone(const cap_channel_t *chan); +#else +static inline cap_channel_t * +cap_clone(const cap_channel_t *chan) +{ + cap_channel_t *newchan; + + newchan = cap_init(); + if (newchan == NULL) { + return (NULL); + } + + if (chan->cch_fd == -1) { + newchan->cch_fd = -1; + } else { + newchan->cch_fd = dup(chan->cch_fd); + if (newchan->cch_fd < 0) { + free(newchan); + newchan = NULL; + } + } + newchan->cch_flags = chan->cch_flags; + + return (newchan); +} +#endif + +/* + * The function closes the given capability. + */ +#ifdef WITH_CASPER +void cap_close(cap_channel_t *chan); +#else +static inline void +cap_close(cap_channel_t *chan) +{ + + if (chan->cch_fd >= 0) { + close(chan->cch_fd); + } + free(chan); +} +#endif + +/* + * The function returns socket descriptor associated with the given + * cap_channel_t for use with select(2)/kqueue(2)/etc. + */ +#ifdef WITH_CASPER +int cap_sock(const cap_channel_t *chan); +#else +#define cap_sock(chan) (chan->cch_fd) +#endif + +/* + * The function limits the given capability. + * It always destroys 'limits' on return. + */ +#ifdef WITH_CASPER +int cap_limit_set(const cap_channel_t *chan, nvlist_t *limits); +#else +static inline int +cap_limit_set(const cap_channel_t *chan __unused, + nvlist_t *limits __unused) +{ + + return (0); +} +#endif + +/* + * The function returns current limits of the given capability. + */ +#ifdef WITH_CASPER +int cap_limit_get(const cap_channel_t *chan, nvlist_t **limitsp); +#else +static inline int +cap_limit_get(const cap_channel_t *chan __unused, nvlist_t **limitsp) +{ + + *limitsp = nvlist_create(channel_nvlist_flags(chan)); + return (0); +} +#endif + +/* + * Function sends nvlist over the given capability. + */ +#ifdef WITH_CASPER +int cap_send_nvlist(const cap_channel_t *chan, const nvlist_t *nvl); +#else +#define cap_send_nvlist(chan, nvl) (0) +#endif + +/* + * Function receives nvlist over the given capability. + */ +#ifdef WITH_CASPER +nvlist_t *cap_recv_nvlist(const cap_channel_t *chan); +#else +#define cap_recv_nvlist(chan) (nvlist_create(chan->cch_flags)) +#endif + +/* + * Function sends the given nvlist, destroys it and receives new nvlist in + * response over the given capability. + */ +#ifdef WITH_CASPER +nvlist_t *cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl); +#else +static inline nvlist_t * +cap_xfer_nvlist(const cap_channel_t *chan, nvlist_t *nvl) +{ + + nvlist_destroy(nvl); + return (nvlist_create(channel_nvlist_flags(chan))); +} +#endif + +__END_DECLS + +#endif /* !_LIBCASPER_H_ */ diff --git a/lib/libcasper/libcasper/libcasper_impl.c b/lib/libcasper/libcasper/libcasper_impl.c new file mode 100644 index 000000000000..d457816e013b --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_impl.c @@ -0,0 +1,71 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stdlib.h> + +#include "libcasper_impl.h" + +bool +fd_is_valid(int fd) +{ + + return (fcntl(fd, F_GETFL) != -1 || errno != EBADF); +} + +void +fd_fix_environment(int *fdp) +{ + int nullfd, nfd; + + if (*fdp > STDERR_FILENO) + return; + + nullfd = open(_PATH_DEVNULL, O_RDWR); + if (nullfd == -1) + errx(1, "Unable to open %s", _PATH_DEVNULL); + + while (*fdp <= STDERR_FILENO) { + nfd = dup(*fdp); + if (nfd == -1) + errx(1, "Unable to secure fd"); + if (dup2(nullfd, *fdp) == -1) + errx(1, "Unable to secure fd"); + *fdp = nfd; + } + + close(nullfd); +} + diff --git a/lib/libcasper/libcasper/libcasper_impl.h b/lib/libcasper/libcasper/libcasper_impl.h new file mode 100644 index 000000000000..5f0aacf2afa8 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_impl.h @@ -0,0 +1,84 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LIBCASPER_IMPL_H_ +#define _LIBCASPER_IMPL_H_ + +#include <stdbool.h> + +#include "libcasper.h" +#include "libcasper_service.h" + +struct service; +struct service_connection; + +bool fd_is_valid(int fd); +void fd_fix_environment(int *fdp); + +/* Private service functions. */ +struct service *service_alloc(const char *name, + service_limit_func_t *limitfunc, + service_command_func_t *commandfunc, uint64_t flags); +void service_free(struct service *service); +void service_message(struct service *service, + struct service_connection *sconn); +void service_start(struct service *service, int sock, int procfd); +const char *service_name(struct service *service); +int service_get_channel_flags(struct service *service); + +/* Private service connection functions. */ +struct service_connection *service_connection_add(struct service *service, + int sock, const nvlist_t *limits); +void service_connection_remove( + struct service *service, + struct service_connection *sconn); +int service_connection_clone( + struct service *service, + struct service_connection *sconn); +struct service_connection *service_connection_first( + struct service *service); +struct service_connection *service_connection_next( + struct service_connection *sconn); +cap_channel_t *service_connection_get_chan( + const struct service_connection *sconn); +int service_connection_get_sock( + const struct service_connection *sconn); +const nvlist_t *service_connection_get_limits( + const struct service_connection *sconn); +void service_connection_set_limits( + struct service_connection *sconn, + nvlist_t *limits); + +/* Private libcasper functions. */ +void casper_main_loop(int fd); + +#endif /* !_LIBCASPER_IMPL_H_ */ diff --git a/lib/libcasper/libcasper/libcasper_service.3 b/lib/libcasper/libcasper/libcasper_service.3 new file mode 100644 index 000000000000..201c19c70234 --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_service.3 @@ -0,0 +1,119 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd November 15, 2021 +.Dt LIBCASPER 3 +.Os +.Sh NAME +.Nm CREATE_SERVICE +.Nd "casper service declaration macro" +.Sh LIBRARY +.Lb libcasper +.Sh SYNOPSIS +.In sys/nv.h +.In libcasper.h +.In libcasper_service.h +.Bd -literal +typedef int service_limit_func_t(const nvlist_t *, const nvlist_t *); + +typedef int service_command_func_t(const char *, const nvlist_t *, nvlist_t *, + nvlist_t *); + +.Ed +.Fn CREATE_SERVICE "name" "limit_func" "command_func" "flags" +.Sh DESCRIPTION +The +.Nm CREATE_SERVICE +macro is used to create a new casper service. +The +.Fa name +is a string containing the service name, which will be used in the +.Xr cap_service_open 3 , +function to identify it. +.Pp +The +.Fa limit_func +is a function of type +.Li service_limit_func_t +where the first argument of the function contains an +.Xr nvlist 9 , +old service limits and +the second argument contains the new limits. +If the service was not limited then the old limits will be set to +.Dv NULL . +This function must not allow the extension of service limits. +The +.Fa command_func +is a function of type +.Li service_command_func_t +where the first argument is the name of the command that should be executed. +The first +.Xr nvlist 9 +contains the current limits and the second contains an +.Xr nvlist 9 +with the current request. +The last argument contains a return value +.Xr nvlist 9 +which contains the response from casper. +.Pp +The +.Fa flags +argument defines the limits of the service. +The supported flags are: +.Bl -ohang -offset indent +.It CASPER_SERVICE_STDIO +The casper service has access to the stdio descriptors from the process it was +spawned from. +.It CASPER_SERVICE_FD +The casper service has access to all of the descriptors, +besides the stdio descriptors, +from the process it was spawned from. +.It CASPER_SERVICE_NO_UNIQ_LIMITS +The whole casper communication is using an +.Xr nvlist 9 +with the +.Dv NV_FLAG_NO_UNIQUE +flag. +.El +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr libcasper 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm libcasper +library first appeared in +.Fx 10.3 . +.Sh AUTHORS +The +.Nm libcasper +library was implemented by +.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net +under sponsorship from the FreeBSD Foundation. +The +.Nm libcasper +new architecture was implemented by +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org +. diff --git a/lib/libcasper/libcasper/libcasper_service.c b/lib/libcasper/libcasper/libcasper_service.c new file mode 100644 index 000000000000..df58f48d78eb --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_service.c @@ -0,0 +1,289 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * Copyright (c) 2017 Robert N. M. Watson + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/nv.h> + +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libcasper_impl.h" +#include "zygote.h" + +struct casper_service { + struct service *cs_service; + TAILQ_ENTRY(casper_service) cs_next; +}; + +static TAILQ_HEAD(, casper_service) casper_services = + TAILQ_HEAD_INITIALIZER(casper_services); + +#define CORE_CASPER_NAME "core.casper" +#define CSERVICE_IS_CORE(service) \ + (strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0) + +static struct casper_service * +service_find(const char *name) +{ + struct casper_service *casserv; + + TAILQ_FOREACH(casserv, &casper_services, cs_next) { + if (strcmp(service_name(casserv->cs_service), name) == 0) + break; + } + return (casserv); +} + +struct casper_service * +service_register(const char *name, service_limit_func_t *limitfunc, + service_command_func_t *commandfunc, uint64_t flags) +{ + struct casper_service *casserv; + + if (commandfunc == NULL) + return (NULL); + if (name == NULL || name[0] == '\0') + return (NULL); + if (service_find(name) != NULL) + return (NULL); + + casserv = malloc(sizeof(*casserv)); + if (casserv == NULL) + return (NULL); + + casserv->cs_service = service_alloc(name, limitfunc, commandfunc, + flags); + if (casserv->cs_service == NULL) { + free(casserv); + return (NULL); + } + TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next); + + return (casserv); +} + +static bool +casper_allowed_service(const nvlist_t *limits, const char *service) +{ + + if (limits == NULL) + return (true); + + if (nvlist_exists_null(limits, service)) + return (true); + + return (false); +} + +static int +casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + int type; + void *cookie; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!casper_allowed_service(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +void +service_execute(int chanfd) +{ + struct casper_service *casserv; + struct service *service; + const char *servname; + nvlist_t *nvl; + int procfd; + + nvl = nvlist_recv(chanfd, 0); + if (nvl == NULL) + _exit(1); + if (!nvlist_exists_string(nvl, "service")) + _exit(1); + servname = nvlist_get_string(nvl, "service"); + casserv = service_find(servname); + if (casserv == NULL) + _exit(1); + service = casserv->cs_service; + procfd = nvlist_take_descriptor(nvl, "procfd"); + nvlist_destroy(nvl); + + service_start(service, chanfd, procfd); + /* Not reached. */ + _exit(1); +} + +static int +casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct casper_service *casserv; + const char *servname; + nvlist_t *nvl; + int chanfd, procfd, error; + + if (strcmp(cmd, "open") != 0) + return (EINVAL); + if (!nvlist_exists_string(nvlin, "service")) + return (EINVAL); + + servname = nvlist_get_string(nvlin, "service"); + casserv = service_find(servname); + if (casserv == NULL) + return (ENOENT); + + if (!casper_allowed_service(limits, servname)) + return (ENOTCAPABLE); + + if (zygote_clone_service_execute(&chanfd, &procfd) == -1) + return (errno); + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "service", servname); + nvlist_move_descriptor(nvl, "procfd", procfd); + if (nvlist_send(chanfd, nvl) == -1) { + error = errno; + nvlist_destroy(nvl); + close(chanfd); + return (error); + } + nvlist_destroy(nvl); + + nvlist_move_descriptor(nvlout, "chanfd", chanfd); + nvlist_add_number(nvlout, "chanflags", + service_get_channel_flags(casserv->cs_service)); + + return (0); +} + +static void +service_register_core(int fd) +{ + struct casper_service *casserv; + struct service_connection *sconn; + + casserv = service_register(CORE_CASPER_NAME, casper_limit, + casper_command, 0); + sconn = service_connection_add(casserv->cs_service, fd, NULL); + if (sconn == NULL) { + close(fd); + abort(); + } +} + +void +casper_main_loop(int fd) +{ + fd_set fds; + struct casper_service *casserv; + struct service_connection *sconn, *sconntmp; + int sock, maxfd, ret; + + if (zygote_init() < 0) + _exit(1); + + /* + * Register core services. + */ + service_register_core(fd); + + for (;;) { + FD_ZERO(&fds); + FD_SET(fd, &fds); + maxfd = -1; + TAILQ_FOREACH(casserv, &casper_services, cs_next) { + /* We handle only core services. */ + if (!CSERVICE_IS_CORE(casserv)) + continue; + for (sconn = service_connection_first(casserv->cs_service); + sconn != NULL; + sconn = service_connection_next(sconn)) { + sock = service_connection_get_sock(sconn); + FD_SET(sock, &fds); + maxfd = sock > maxfd ? sock : maxfd; + } + } + if (maxfd == -1) { + /* Nothing to do. */ + _exit(0); + } + maxfd++; + + + assert(maxfd <= (int)FD_SETSIZE); + ret = select(maxfd, &fds, NULL, NULL, NULL); + assert(ret == -1 || ret > 0); /* select() cannot timeout */ + if (ret == -1) { + if (errno == EINTR) + continue; + _exit(1); + } + + TAILQ_FOREACH(casserv, &casper_services, cs_next) { + /* We handle only core services. */ + if (!CSERVICE_IS_CORE(casserv)) + continue; + for (sconn = service_connection_first(casserv->cs_service); + sconn != NULL; sconn = sconntmp) { + /* + * Prepare for connection to be removed from + * the list on failure. + */ + sconntmp = service_connection_next(sconn); + sock = service_connection_get_sock(sconn); + if (FD_ISSET(sock, &fds)) { + service_message(casserv->cs_service, + sconn); + } + } + } + } +} diff --git a/lib/libcasper/libcasper/libcasper_service.h b/lib/libcasper/libcasper/libcasper_service.h new file mode 100644 index 000000000000..79de7420253d --- /dev/null +++ b/lib/libcasper/libcasper/libcasper_service.h @@ -0,0 +1,65 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LIBCASPER_SERVICE_H_ +#define _LIBCASPER_SERVICE_H_ + +#ifndef _NVLIST_T_DECLARED +#define _NVLIST_T_DECLARED +struct nvlist; + +typedef struct nvlist nvlist_t; +#endif + +#define CASPER_SERVICE_STDIO 0x00000001 +#define CASPER_SERVICE_FD 0x00000002 +#define CASPER_SERVICE_NO_UNIQ_LIMITS 0x00000004 + +typedef int service_limit_func_t(const nvlist_t *, const nvlist_t *); +typedef int service_command_func_t(const char *cmd, const nvlist_t *, + nvlist_t *, nvlist_t *); + +struct casper_service *service_register(const char *name, + service_limit_func_t *limitfunc, service_command_func_t *commandfunc, + uint64_t flags); + +#define __constructor __attribute__((constructor)) +#define CREATE_SERVICE(name, limit_func, command_func, flags) \ + static __constructor void \ + init_casper_service(void) \ + { \ + \ + (void)service_register(name, limit_func, command_func, \ + flags); \ + } + +#endif /* !_LIBCASPER_SERVICE_H_ */ diff --git a/lib/libcasper/libcasper/service.c b/lib/libcasper/libcasper/service.c new file mode 100644 index 000000000000..70418db50085 --- /dev/null +++ b/lib/libcasper/libcasper/service.c @@ -0,0 +1,472 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/nv.h> + +#include <assert.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include "libcasper.h" +#include "libcasper_impl.h" + +/* + * Currently there is only one service_connection per service. + * In the future we may want multiple connections from multiple clients + * per one service instance, but it has to be carefully designed. + * The problem is that we may restrict/sandbox service instance according + * to the limits provided. When new connection comes in with different + * limits we won't be able to access requested resources. + * Not to mention one process will serve to multiple mutually untrusted + * clients and compromise of this service instance by one of its clients + * can lead to compromise of the other clients. + */ + +/* + * Client connections to the given service. + */ +#define SERVICE_CONNECTION_MAGIC 0x5e91c0ec +struct service_connection { + int sc_magic; + cap_channel_t *sc_chan; + nvlist_t *sc_limits; + TAILQ_ENTRY(service_connection) sc_next; +}; + +#define SERVICE_MAGIC 0x5e91ce +struct service { + int s_magic; + char *s_name; + uint64_t s_flags; + service_limit_func_t *s_limit; + service_command_func_t *s_command; + TAILQ_HEAD(, service_connection) s_connections; +}; + +struct service * +service_alloc(const char *name, service_limit_func_t *limitfunc, + service_command_func_t *commandfunc, uint64_t flags) +{ + struct service *service; + + service = malloc(sizeof(*service)); + if (service == NULL) + return (NULL); + service->s_name = strdup(name); + if (service->s_name == NULL) { + free(service); + return (NULL); + } + service->s_limit = limitfunc; + service->s_command = commandfunc; + service->s_flags = flags; + TAILQ_INIT(&service->s_connections); + service->s_magic = SERVICE_MAGIC; + + return (service); +} + +void +service_free(struct service *service) +{ + struct service_connection *sconn; + + assert(service->s_magic == SERVICE_MAGIC); + + service->s_magic = 0; + while ((sconn = service_connection_first(service)) != NULL) + service_connection_remove(service, sconn); + free(service->s_name); + free(service); +} + +struct service_connection * +service_connection_add(struct service *service, int sock, + const nvlist_t *limits) +{ + struct service_connection *sconn; + int serrno; + + assert(service->s_magic == SERVICE_MAGIC); + + sconn = malloc(sizeof(*sconn)); + if (sconn == NULL) + return (NULL); + sconn->sc_chan = cap_wrap(sock, + service_get_channel_flags(service)); + if (sconn->sc_chan == NULL) { + serrno = errno; + free(sconn); + errno = serrno; + return (NULL); + } + if (limits == NULL) { + sconn->sc_limits = NULL; + } else { + sconn->sc_limits = nvlist_clone(limits); + if (sconn->sc_limits == NULL) { + serrno = errno; + (void)cap_unwrap(sconn->sc_chan, NULL); + free(sconn); + errno = serrno; + return (NULL); + } + } + sconn->sc_magic = SERVICE_CONNECTION_MAGIC; + TAILQ_INSERT_TAIL(&service->s_connections, sconn, sc_next); + return (sconn); +} + +void +service_connection_remove(struct service *service, + struct service_connection *sconn) +{ + + assert(service->s_magic == SERVICE_MAGIC); + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + + TAILQ_REMOVE(&service->s_connections, sconn, sc_next); + sconn->sc_magic = 0; + nvlist_destroy(sconn->sc_limits); + cap_close(sconn->sc_chan); + free(sconn); +} + +int +service_connection_clone(struct service *service, + struct service_connection *sconn) +{ + struct service_connection *newsconn; + int serrno, sock[2]; + + if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sock) < 0) + return (-1); + + newsconn = service_connection_add(service, sock[0], + service_connection_get_limits(sconn)); + if (newsconn == NULL) { + serrno = errno; + close(sock[0]); + close(sock[1]); + errno = serrno; + return (-1); + } + + return (sock[1]); +} + +struct service_connection * +service_connection_first(struct service *service) +{ + struct service_connection *sconn; + + assert(service->s_magic == SERVICE_MAGIC); + + sconn = TAILQ_FIRST(&service->s_connections); + assert(sconn == NULL || + sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + return (sconn); +} + +struct service_connection * +service_connection_next(struct service_connection *sconn) +{ + + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + + sconn = TAILQ_NEXT(sconn, sc_next); + assert(sconn == NULL || + sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + return (sconn); +} + +cap_channel_t * +service_connection_get_chan(const struct service_connection *sconn) +{ + + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + + return (sconn->sc_chan); +} + +int +service_connection_get_sock(const struct service_connection *sconn) +{ + + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + + return (cap_sock(sconn->sc_chan)); +} + +const nvlist_t * +service_connection_get_limits(const struct service_connection *sconn) +{ + + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + + return (sconn->sc_limits); +} + +void +service_connection_set_limits(struct service_connection *sconn, + nvlist_t *limits) +{ + + assert(sconn->sc_magic == SERVICE_CONNECTION_MAGIC); + + nvlist_destroy(sconn->sc_limits); + sconn->sc_limits = limits; +} + +void +service_message(struct service *service, struct service_connection *sconn) +{ + nvlist_t *nvlin, *nvlout; + const char *cmd; + int error, flags; + + flags = 0; + if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0) + flags = NV_FLAG_NO_UNIQUE; + + nvlin = cap_recv_nvlist(service_connection_get_chan(sconn)); + if (nvlin == NULL) { + service_connection_remove(service, sconn); + return; + } + + error = EDOOFUS; + nvlout = nvlist_create(flags); + + cmd = nvlist_get_string(nvlin, "cmd"); + if (strcmp(cmd, "limit_set") == 0) { + nvlist_t *nvllim; + + nvllim = nvlist_take_nvlist(nvlin, "limits"); + if (service->s_limit == NULL) { + error = EOPNOTSUPP; + } else { + error = service->s_limit( + service_connection_get_limits(sconn), nvllim); + } + if (error == 0) { + service_connection_set_limits(sconn, nvllim); + /* Function consumes nvllim. */ + } else { + nvlist_destroy(nvllim); + } + } else if (strcmp(cmd, "limit_get") == 0) { + const nvlist_t *nvllim; + + nvllim = service_connection_get_limits(sconn); + if (nvllim != NULL) + nvlist_add_nvlist(nvlout, "limits", nvllim); + else + nvlist_add_null(nvlout, "limits"); + error = 0; + } else if (strcmp(cmd, "clone") == 0) { + int sock; + + sock = service_connection_clone(service, sconn); + if (sock == -1) { + error = errno; + } else { + nvlist_move_descriptor(nvlout, "sock", sock); + error = 0; + } + } else { + error = service->s_command(cmd, + service_connection_get_limits(sconn), nvlin, nvlout); + } + + nvlist_destroy(nvlin); + nvlist_add_number(nvlout, "error", (uint64_t)error); + + if (cap_send_nvlist(service_connection_get_chan(sconn), nvlout) == -1) + service_connection_remove(service, sconn); + + nvlist_destroy(nvlout); +} + +static int +fd_add(fd_set *fdsp, int maxfd, int fd) +{ + + FD_SET(fd, fdsp); + return (fd > maxfd ? fd : maxfd); +} + +const char * +service_name(struct service *service) +{ + + assert(service->s_magic == SERVICE_MAGIC); + return (service->s_name); +} + +int +service_get_channel_flags(struct service *service) +{ + int flags; + + assert(service->s_magic == SERVICE_MAGIC); + flags = 0; + + if ((service->s_flags & CASPER_SERVICE_NO_UNIQ_LIMITS) != 0) + flags |= CASPER_NO_UNIQ; + + return (flags); +} + +static void +stdnull(void) +{ + int fd; + + fd = open(_PATH_DEVNULL, O_RDWR); + if (fd == -1) + errx(1, "Unable to open %s", _PATH_DEVNULL); + + if (setsid() == -1) + errx(1, "Unable to detach from session"); + + if (dup2(fd, STDIN_FILENO) == -1) + errx(1, "Unable to cover stdin"); + if (dup2(fd, STDOUT_FILENO) == -1) + errx(1, "Unable to cover stdout"); + if (dup2(fd, STDERR_FILENO) == -1) + errx(1, "Unable to cover stderr"); + + if (fd > STDERR_FILENO) + close(fd); +} + +static void +service_clean(int *sockp, int *procfdp, uint64_t flags) +{ + int fd, maxfd, minfd; + + fd_fix_environment(sockp); + fd_fix_environment(procfdp); + + assert(*sockp > STDERR_FILENO); + assert(*procfdp > STDERR_FILENO); + assert(*sockp != *procfdp); + + if ((flags & CASPER_SERVICE_STDIO) == 0) + stdnull(); + + if ((flags & CASPER_SERVICE_FD) == 0) { + if (*procfdp > *sockp) { + maxfd = *procfdp; + minfd = *sockp; + } else { + maxfd = *sockp; + minfd = *procfdp; + } + + for (fd = STDERR_FILENO + 1; fd < maxfd; fd++) { + if (fd != minfd) + close(fd); + } + closefrom(maxfd + 1); + } +} + +void +service_start(struct service *service, int sock, int procfd) +{ + struct service_connection *sconn, *sconntmp; + fd_set fds; + int maxfd, nfds; + + assert(service != NULL); + assert(service->s_magic == SERVICE_MAGIC); + setproctitle("%s", service->s_name); + service_clean(&sock, &procfd, service->s_flags); + + if (service_connection_add(service, sock, NULL) == NULL) + _exit(1); + + for (;;) { + FD_ZERO(&fds); + maxfd = -1; + for (sconn = service_connection_first(service); sconn != NULL; + sconn = service_connection_next(sconn)) { + maxfd = fd_add(&fds, maxfd, + service_connection_get_sock(sconn)); + } + + assert(maxfd >= 0); + assert(maxfd + 1 <= (int)FD_SETSIZE); + nfds = select(maxfd + 1, &fds, NULL, NULL, NULL); + if (nfds < 0) { + if (errno != EINTR) + _exit(1); + continue; + } else if (nfds == 0) { + /* Timeout. */ + abort(); + } + + for (sconn = service_connection_first(service); sconn != NULL; + sconn = sconntmp) { + /* + * Prepare for connection to be removed from the list + * on failure. + */ + sconntmp = service_connection_next(sconn); + if (FD_ISSET(service_connection_get_sock(sconn), &fds)) + service_message(service, sconn); + } + if (service_connection_first(service) == NULL) { + /* + * No connections left, exiting. + */ + break; + } + } + + _exit(0); +} diff --git a/lib/libcasper/libcasper/zygote.c b/lib/libcasper/libcasper/zygote.c new file mode 100644 index 000000000000..976e391d8dcb --- /dev/null +++ b/lib/libcasper/libcasper/zygote.c @@ -0,0 +1,219 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * Copyright (c) 2017 Robert N. M. Watson + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * All rights reserved. + * This software was developed by SRI International and the University of + * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237) + * ("CTSRD"), as part of the DARPA CRASH research programme. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/capsicum.h> +#include <sys/procdesc.h> +#include <sys/socket.h> +#include <sys/nv.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include "libcasper_impl.h" +#include "zygote.h" + +/* Zygote info. */ +static int zygote_sock = -1; + +#define ZYGOTE_SERVICE_EXECUTE 1 + +int +zygote_clone(uint64_t funcidx, int *chanfdp, int *procfdp) +{ + nvlist_t *nvl; + int error; + + if (zygote_sock == -1) { + /* Zygote didn't start. */ + errno = ENXIO; + return (-1); + } + + nvl = nvlist_create(0); + nvlist_add_number(nvl, "funcidx", funcidx); + nvl = nvlist_xfer(zygote_sock, nvl, 0); + if (nvl == NULL) + return (-1); + if (nvlist_exists_number(nvl, "error")) { + error = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + errno = error; + return (-1); + } + + *chanfdp = nvlist_take_descriptor(nvl, "chanfd"); + *procfdp = nvlist_take_descriptor(nvl, "procfd"); + + nvlist_destroy(nvl); + return (0); +} + +int +zygote_clone_service_execute(int *chanfdp, int *procfdp) +{ + + return (zygote_clone(ZYGOTE_SERVICE_EXECUTE, chanfdp, procfdp)); +} + +/* + * This function creates sandboxes on-demand whoever has access to it via + * 'sock' socket. Function sends two descriptors to the caller: process + * descriptor of the sandbox and socket pair descriptor for communication + * between sandbox and its owner. + */ +static void +zygote_main(int *sockp) +{ + int error, procfd; + int chanfd[2]; + nvlist_t *nvlin, *nvlout; + uint64_t funcidx; + zygote_func_t *func; + pid_t pid; + + fd_fix_environment(sockp); + + assert(*sockp > STDERR_FILENO); + + setproctitle("zygote"); + + for (;;) { + nvlin = nvlist_recv(*sockp, 0); + if (nvlin == NULL) { + if (errno == ENOTCONN) { + /* Casper exited. */ + _exit(0); + } + continue; + } + funcidx = nvlist_get_number(nvlin, "funcidx"); + nvlist_destroy(nvlin); + + switch (funcidx) { + case ZYGOTE_SERVICE_EXECUTE: + func = service_execute; + break; + default: + _exit(0); + } + + /* + * Someone is requesting a new process, create one. + */ + procfd = -1; + chanfd[0] = -1; + chanfd[1] = -1; + error = 0; + if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, + chanfd) == -1) { + error = errno; + goto send; + } + pid = pdfork(&procfd, 0); + switch (pid) { + case -1: + /* Failure. */ + error = errno; + break; + case 0: + /* Child. */ + close(*sockp); + close(chanfd[0]); + func(chanfd[1]); + /* NOTREACHED */ + _exit(1); + default: + /* Parent. */ + close(chanfd[1]); + break; + } +send: + nvlout = nvlist_create(0); + if (error != 0) { + nvlist_add_number(nvlout, "error", (uint64_t)error); + if (chanfd[0] >= 0) + close(chanfd[0]); + if (procfd >= 0) + close(procfd); + } else { + nvlist_move_descriptor(nvlout, "chanfd", chanfd[0]); + nvlist_move_descriptor(nvlout, "procfd", procfd); + } + (void)nvlist_send(*sockp, nvlout); + nvlist_destroy(nvlout); + } + /* NOTREACHED */ +} + +int +zygote_init(void) +{ + int serrno, sp[2]; + pid_t pid; + + if (socketpair(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, sp) == -1) + return (-1); + + pid = fork(); + switch (pid) { + case -1: + /* Failure. */ + serrno = errno; + close(sp[0]); + close(sp[1]); + errno = serrno; + return (-1); + case 0: + /* Child. */ + close(sp[0]); + zygote_main(&sp[1]); + /* NOTREACHED */ + abort(); + default: + /* Parent. */ + zygote_sock = sp[0]; + close(sp[1]); + return (0); + } + /* NOTREACHED */ +} diff --git a/lib/libcasper/libcasper/zygote.h b/lib/libcasper/libcasper/zygote.h new file mode 100644 index 000000000000..871da6f04182 --- /dev/null +++ b/lib/libcasper/libcasper/zygote.h @@ -0,0 +1,47 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012 The FreeBSD Foundation + * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _ZYGOTE_H_ +#define _ZYGOTE_H_ + +typedef void zygote_func_t(int); + +int zygote_init(void); +int zygote_clone(uint64_t funcidx, int *chanfdp, int *procfdp); +int zygote_clone_service_execute(int *chanfdp, int *procfdp); + +/* + * Functions reachable via zygote_clone(). + */ +zygote_func_t service_execute; + +#endif /* !_ZYGOTE_H_ */ diff --git a/lib/libcasper/services/Makefile b/lib/libcasper/services/Makefile new file mode 100644 index 000000000000..c86b199f879b --- /dev/null +++ b/lib/libcasper/services/Makefile @@ -0,0 +1,16 @@ +.include <src.opts.mk> + +SUBDIR= cap_dns +SUBDIR+= cap_fileargs +SUBDIR+= cap_grp +SUBDIR+= cap_net +SUBDIR+= cap_netdb +SUBDIR+= cap_pwd +SUBDIR+= cap_sysctl +SUBDIR+= cap_syslog + +SUBDIR.${MK_TESTS}+= tests + +SUBDIR_PARALLEL= + +.include <bsd.subdir.mk> diff --git a/lib/libcasper/services/Makefile.depend b/lib/libcasper/services/Makefile.depend new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/lib/libcasper/services/Makefile.depend diff --git a/lib/libcasper/services/Makefile.inc b/lib/libcasper/services/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/lib/libcasper/services/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/lib/libcasper/services/cap_dns/Makefile b/lib/libcasper/services/cap_dns/Makefile new file mode 100644 index 000000000000..4b11c97d29e5 --- /dev/null +++ b/lib/libcasper/services/cap_dns/Makefile @@ -0,0 +1,31 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 2 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_dns + +SRCS= cap_dns.c +.endif + +INCS= cap_dns.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_dns.3 + +MLINKS+=cap_dns.3 libcap_dns.3 +MLINKS+=cap_dns.3 cap_dns_type_limit.3 +MLINKS+=cap_dns.3 cap_dns_family_limit.3 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_dns/Makefile.depend b/lib/libcasper/services/cap_dns/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_dns/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_dns/cap_dns.3 b/lib/libcasper/services/cap_dns/cap_dns.3 new file mode 100644 index 000000000000..d6bbb6bd1263 --- /dev/null +++ b/lib/libcasper/services/cap_dns/cap_dns.3 @@ -0,0 +1,241 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd August 15, 2020 +.Dt CAP_DNS 3 +.Os +.Sh NAME +.Nm cap_getaddrinfo , +.Nm cap_getnameinfo , +.Nm cap_gethostbyname , +.Nm cap_gethostbyname2 , +.Nm cap_gethostbyaddr , +.Nm cap_dns_type_limit , +.Nm cap_dns_family_limit +.Nd "library for getting network host entry in capability mode" +.Sh LIBRARY +.Lb libcap_dns +.Sh SYNOPSIS +.In sys/nv.h +.In libcasper.h +.In casper/cap_dns.h +.Ft int +.Fn cap_getaddrinfo "cap_channel_t *chan" "const char *hostname" "const char *servname" "const struct addrinfo *hints" "struct addrinfo **res" +.Ft int +.Fn cap_getnameinfo "cap_channel_t *chan" "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" +.Ft "struct hostent *" +.Fn cap_gethostbyname "const cap_channel_t *chan" "const char *name" +.Ft "struct hostent *" +.Fn cap_gethostbyname2 "const cap_channel_t *chan" "const char *name" "int af" +.Ft "struct hostent *" +.Fn cap_gethostbyaddr "const cap_channel_t *chan" "const void *addr" "socklen_t len" "int af" +.Ft "int" +.Fn cap_dns_type_limit "cap_channel_t *chan" "const char * const *types" "size_t ntypes" +.Ft "int" +.Fn cap_dns_family_limit "const cap_channel_t *chan" "const int *families" "size_t nfamilies" +.Sh DESCRIPTION +.Bf -symbolic +This service is obsolete and +.Xr cap_net 3 +should be used instead. +The +.Fn cap_getaddrinfo , +and +.Fn cap_getnameinfo , +functions are preferred over the +.Fn cap_gethostbyname , +.Fn cap_gethostbyname2 , +and +.Fn cap_gethostbyaddr +functions. +.Ef +.Pp +The functions +.Fn cap_gethostbyname , +.Fn cap_gethostbyname2 , +.Fn cep_gethostbyaddr +and +.Fn cap_getnameinfo +are respectively equivalent to +.Xr gethostbyname 3 , +.Xr gethostbyname2 3 , +.Xr gethostbyaddr 3 +and +.Xr getnameinfo 3 +except that the connection to the +.Nm system.dns +service needs to be provided. +.Pp +The +.Fn cap_dns_type_limit +function limits the functions allowed in the service. +The +.Fa types +variable can be set to +.Dv ADDR2NAME +or +.Dv NAME2ADDR . +See the +.Sx LIMITS +section for more details. +The +.Fa ntpyes +variable contains the number of +.Fa types +provided. +.Pp +The +.Fn cap_dns_family_limit +functions allows to limit address families. +For details see +.Sx LIMITS . +The +.Fa nfamilies +variable contains the number of +.Fa families +provided. +.Sh LIMITS +The preferred way of setting limits is to use the +.Fn cap_dns_type_limit +and +.Fn cap_dns_family_limit +functions, but the limits of service can be set also using +.Xr cap_limit_set 3 . +The +.Xr nvlist 9 +for that function can contain the following values and types: +.Bl -ohang -offset indent +.It type ( NV_TYPE_STRING ) +The +.Va type +can have two values: +.Dv ADDR2NAME +or +.Dv NAME2ADDR . +The +.Dv ADDR2NAME +means that reverse DNS lookups are allowed with +.Fn cap_getnameinfo +and +.Fn cap_gethostbyaddr +functions. +In case when +.Va type +is set to +.Dv NAME2ADDR +the name resolution is allowed with +.Fn cap_getaddrinfo , +.Fn cap_gethostbyname , +and +.Fn cap_gethostbyname2 +functions. +.It family ( NV_TYPE_NUMBER ) +The +.Va family +limits service to one of the address families (e.g. +.Dv AF_INET , AF_INET6 , +etc.). +.El +.Sh EXAMPLES +The following example first opens a capability to casper and then uses this +capability to create the +.Nm system.dns +casper service and uses it to resolve an IP address. +.Bd -literal +cap_channel_t *capcas, *capdns; +int familylimit, error; +const char *ipstr = "127.0.0.1"; +const char *typelimit = "ADDR2NAME"; +char hname[NI_MAXHOST]; +struct addrinfo hints, *res; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Cache NLA for gai_strerror. */ +caph_cache_catpages(); + +/* Enter capability mode sandbox. */ +if (caph_enter() < 0) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.dns service. */ +capdns = cap_service_open(capcas, "system.dns"); +if (capdns == NULL) + err(1, "Unable to open system.dns service"); + +/* Close Casper capability, we don't need it anymore. */ +cap_close(capcas); + +/* Limit system.dns to reserve IPv4 addresses */ +familylimit = AF_INET; +if (cap_dns_family_limit(capdns, &familylimit, 1) < 0) + err(1, "Unable to limit access to the system.dns service"); + +/* Convert IP address in C-string to struct sockaddr. */ +memset(&hints, 0, sizeof(hints)); +hints.ai_family = familylimit; +hints.ai_flags = AI_NUMERICHOST; +error = cap_getaddrinfo(capdns, ipstr, NULL, &hints, &res); +if (error != 0) + errx(1, "cap_getaddrinfo(): %s: %s", ipstr, gai_strerror(error)); + +/* Limit system.dns to reverse DNS lookups. */ +if (cap_dns_type_limit(capdns, &typelimit, 1) < 0) + err(1, "Unable to limit access to the system.dns service"); + +/* Find hostname for the given IP address. */ +error = cap_getnameinfo(capdns, res->ai_addr, res->ai_addrlen, hname, sizeof(hname), + NULL, 0, 0); +if (error != 0) + errx(1, "cap_getnameinfo(): %s: %s", ipstr, gai_strerror(error)); + +printf("Name associated with %s is %s.\\n", ipstr, hname); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr caph_enter 3 , +.Xr err 3 , +.Xr gethostbyaddr 3 , +.Xr gethostbyname 3 , +.Xr gethostbyname2 3 , +.Xr getnameinfo 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm cap_dns +service first appeared in +.Fx 10.3 . +.Sh AUTHORS +The +.Nm cap_dns +service was implemented by +.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net +under sponsorship from the FreeBSD Foundation. +.Pp +This manual page was written by +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org . diff --git a/lib/libcasper/services/cap_dns/cap_dns.c b/lib/libcasper/services/cap_dns/cap_dns.c new file mode 100644 index 000000000000..8681f0baef40 --- /dev/null +++ b/lib/libcasper/services/cap_dns/cap_dns.c @@ -0,0 +1,767 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012-2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/dnv.h> +#include <sys/nv.h> +#include <netinet/in.h> + +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_dns.h" + +static struct hostent hent; + +static void +hostent_free(struct hostent *hp) +{ + unsigned int ii; + + free(hp->h_name); + hp->h_name = NULL; + if (hp->h_aliases != NULL) { + for (ii = 0; hp->h_aliases[ii] != NULL; ii++) + free(hp->h_aliases[ii]); + free(hp->h_aliases); + hp->h_aliases = NULL; + } + if (hp->h_addr_list != NULL) { + for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) + free(hp->h_addr_list[ii]); + free(hp->h_addr_list); + hp->h_addr_list = NULL; + } +} + +static struct hostent * +hostent_unpack(const nvlist_t *nvl, struct hostent *hp) +{ + unsigned int ii, nitems; + char nvlname[64]; + int n; + + hostent_free(hp); + + hp->h_name = strdup(nvlist_get_string(nvl, "name")); + if (hp->h_name == NULL) + goto fail; + hp->h_addrtype = (int)nvlist_get_number(nvl, "addrtype"); + hp->h_length = (int)nvlist_get_number(nvl, "length"); + + nitems = (unsigned int)nvlist_get_number(nvl, "naliases"); + hp->h_aliases = calloc(nitems + 1, sizeof(hp->h_aliases[0])); + if (hp->h_aliases == NULL) + goto fail; + for (ii = 0; ii < nitems; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + hp->h_aliases[ii] = + strdup(nvlist_get_string(nvl, nvlname)); + if (hp->h_aliases[ii] == NULL) + goto fail; + } + hp->h_aliases[ii] = NULL; + + nitems = (unsigned int)nvlist_get_number(nvl, "naddrs"); + hp->h_addr_list = calloc(nitems + 1, sizeof(hp->h_addr_list[0])); + if (hp->h_addr_list == NULL) + goto fail; + for (ii = 0; ii < nitems; ii++) { + hp->h_addr_list[ii] = malloc(hp->h_length); + if (hp->h_addr_list[ii] == NULL) + goto fail; + n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + bcopy(nvlist_get_binary(nvl, nvlname, NULL), + hp->h_addr_list[ii], hp->h_length); + } + hp->h_addr_list[ii] = NULL; + + return (hp); +fail: + hostent_free(hp); + h_errno = NO_RECOVERY; + return (NULL); +} + +struct hostent * +cap_gethostbyname(cap_channel_t *chan, const char *name) +{ + + return (cap_gethostbyname2(chan, name, AF_INET)); +} + +struct hostent * +cap_gethostbyname2(cap_channel_t *chan, const char *name, int type) +{ + struct hostent *hp; + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "gethostbyname"); + nvlist_add_number(nvl, "family", (uint64_t)type); + nvlist_add_string(nvl, "name", name); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + h_errno = NO_RECOVERY; + return (NULL); + } + if (nvlist_get_number(nvl, "error") != 0) { + h_errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (NULL); + } + + hp = hostent_unpack(nvl, &hent); + nvlist_destroy(nvl); + return (hp); +} + +struct hostent * +cap_gethostbyaddr(cap_channel_t *chan, const void *addr, socklen_t len, + int type) +{ + struct hostent *hp; + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "gethostbyaddr"); + nvlist_add_binary(nvl, "addr", addr, (size_t)len); + nvlist_add_number(nvl, "family", (uint64_t)type); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + h_errno = NO_RECOVERY; + return (NULL); + } + if (nvlist_get_number(nvl, "error") != 0) { + h_errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (NULL); + } + hp = hostent_unpack(nvl, &hent); + nvlist_destroy(nvl); + return (hp); +} + +static struct addrinfo * +addrinfo_unpack(const nvlist_t *nvl) +{ + struct addrinfo *ai; + const void *addr; + size_t addrlen; + const char *canonname; + + addr = nvlist_get_binary(nvl, "ai_addr", &addrlen); + ai = malloc(sizeof(*ai) + addrlen); + if (ai == NULL) + return (NULL); + ai->ai_flags = (int)nvlist_get_number(nvl, "ai_flags"); + ai->ai_family = (int)nvlist_get_number(nvl, "ai_family"); + ai->ai_socktype = (int)nvlist_get_number(nvl, "ai_socktype"); + ai->ai_protocol = (int)nvlist_get_number(nvl, "ai_protocol"); + ai->ai_addrlen = (socklen_t)addrlen; + canonname = dnvlist_get_string(nvl, "ai_canonname", NULL); + if (canonname != NULL) { + ai->ai_canonname = strdup(canonname); + if (ai->ai_canonname == NULL) { + free(ai); + return (NULL); + } + } else { + ai->ai_canonname = NULL; + } + ai->ai_addr = (void *)(ai + 1); + bcopy(addr, ai->ai_addr, addrlen); + ai->ai_next = NULL; + + return (ai); +} + +int +cap_getaddrinfo(cap_channel_t *chan, const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct addrinfo *firstai, *prevai, *curai; + unsigned int ii; + const nvlist_t *nvlai; + char nvlname[64]; + nvlist_t *nvl; + int error, n; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "getaddrinfo"); + if (hostname != NULL) + nvlist_add_string(nvl, "hostname", hostname); + if (servname != NULL) + nvlist_add_string(nvl, "servname", servname); + if (hints != NULL) { + nvlist_add_number(nvl, "hints.ai_flags", + (uint64_t)hints->ai_flags); + nvlist_add_number(nvl, "hints.ai_family", + (uint64_t)hints->ai_family); + nvlist_add_number(nvl, "hints.ai_socktype", + (uint64_t)hints->ai_socktype); + nvlist_add_number(nvl, "hints.ai_protocol", + (uint64_t)hints->ai_protocol); + } + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (EAI_MEMORY); + if (nvlist_get_number(nvl, "error") != 0) { + error = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (error); + } + + nvlai = NULL; + firstai = prevai = curai = NULL; + for (ii = 0; ; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "res%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + if (!nvlist_exists_nvlist(nvl, nvlname)) + break; + nvlai = nvlist_get_nvlist(nvl, nvlname); + curai = addrinfo_unpack(nvlai); + if (curai == NULL) + break; + if (prevai != NULL) + prevai->ai_next = curai; + else if (firstai == NULL) + firstai = curai; + prevai = curai; + } + nvlist_destroy(nvl); + if (curai == NULL && nvlai != NULL) { + if (firstai == NULL) + freeaddrinfo(firstai); + return (EAI_MEMORY); + } + + *res = firstai; + return (0); +} + +int +cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, char *serv, size_t servlen, int flags) +{ + nvlist_t *nvl; + int error; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "getnameinfo"); + nvlist_add_number(nvl, "hostlen", (uint64_t)hostlen); + nvlist_add_number(nvl, "servlen", (uint64_t)servlen); + nvlist_add_binary(nvl, "sa", sa, (size_t)salen); + nvlist_add_number(nvl, "flags", (uint64_t)flags); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (EAI_MEMORY); + if (nvlist_get_number(nvl, "error") != 0) { + error = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (error); + } + + if (host != NULL && nvlist_exists_string(nvl, "host")) + strlcpy(host, nvlist_get_string(nvl, "host"), hostlen); + if (serv != NULL && nvlist_exists_string(nvl, "serv")) + strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen); + nvlist_destroy(nvl); + return (0); +} + +static void +limit_remove(nvlist_t *limits, const char *prefix) +{ + const char *name; + size_t prefixlen; + void *cookie; + + prefixlen = strlen(prefix); +again: + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, prefix, prefixlen) == 0) { + nvlist_free(limits, name); + goto again; + } + } +} + +int +cap_dns_type_limit(cap_channel_t *chan, const char * const *types, + size_t ntypes) +{ + nvlist_t *limits; + unsigned int i; + char nvlname[64]; + int n; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) + limits = nvlist_create(0); + else + limit_remove(limits, "type"); + for (i = 0; i < ntypes; i++) { + n = snprintf(nvlname, sizeof(nvlname), "type%u", i); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_string(limits, nvlname, types[i]); + } + return (cap_limit_set(chan, limits)); +} + +int +cap_dns_family_limit(cap_channel_t *chan, const int *families, + size_t nfamilies) +{ + nvlist_t *limits; + unsigned int i; + char nvlname[64]; + int n; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) + limits = nvlist_create(0); + else + limit_remove(limits, "family"); + for (i = 0; i < nfamilies; i++) { + n = snprintf(nvlname, sizeof(nvlname), "family%u", i); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_number(limits, nvlname, (uint64_t)families[i]); + } + return (cap_limit_set(chan, limits)); +} + +/* + * Service functions. + */ +static bool +dns_allowed_type(const nvlist_t *limits, const char *type) +{ + const char *name; + bool notypes; + void *cookie; + + if (limits == NULL) + return (true); + + notypes = true; + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, "type", sizeof("type") - 1) != 0) + continue; + notypes = false; + if (strcmp(nvlist_get_string(limits, name), type) == 0) + return (true); + } + + /* If there are no types at all, allow any type. */ + if (notypes) + return (true); + + return (false); +} + +static bool +dns_allowed_family(const nvlist_t *limits, int family) +{ + const char *name; + bool nofamilies; + void *cookie; + + if (limits == NULL) + return (true); + + nofamilies = true; + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, "family", sizeof("family") - 1) != 0) + continue; + nofamilies = false; + if (family == AF_UNSPEC) + continue; + if (nvlist_get_number(limits, name) == (uint64_t)family) + return (true); + } + + /* If there are no families at all, allow any family. */ + if (nofamilies) + return (true); + + return (false); +} + +static void +hostent_pack(const struct hostent *hp, nvlist_t *nvl) +{ + unsigned int ii; + char nvlname[64]; + int n; + + nvlist_add_string(nvl, "name", hp->h_name); + nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype); + nvlist_add_number(nvl, "length", (uint64_t)hp->h_length); + + if (hp->h_aliases == NULL) { + nvlist_add_number(nvl, "naliases", 0); + } else { + for (ii = 0; hp->h_aliases[ii] != NULL; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_string(nvl, nvlname, hp->h_aliases[ii]); + } + nvlist_add_number(nvl, "naliases", (uint64_t)ii); + } + + if (hp->h_addr_list == NULL) { + nvlist_add_number(nvl, "naddrs", 0); + } else { + for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_binary(nvl, nvlname, hp->h_addr_list[ii], + (size_t)hp->h_length); + } + nvlist_add_number(nvl, "naddrs", (uint64_t)ii); + } +} + +static int +dns_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct hostent *hp; + int family; + + if (!dns_allowed_type(limits, "NAME2ADDR") && + !dns_allowed_type(limits, "NAME")) + return (NO_RECOVERY); + + family = (int)nvlist_get_number(nvlin, "family"); + + if (!dns_allowed_family(limits, family)) + return (NO_RECOVERY); + + hp = gethostbyname2(nvlist_get_string(nvlin, "name"), family); + if (hp == NULL) + return (h_errno); + hostent_pack(hp, nvlout); + return (0); +} + +static int +dns_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct hostent *hp; + const void *addr; + size_t addrsize; + int family; + + if (!dns_allowed_type(limits, "ADDR2NAME") && + !dns_allowed_type(limits, "ADDR")) + return (NO_RECOVERY); + + family = (int)nvlist_get_number(nvlin, "family"); + + if (!dns_allowed_family(limits, family)) + return (NO_RECOVERY); + + addr = nvlist_get_binary(nvlin, "addr", &addrsize); + hp = gethostbyaddr(addr, (socklen_t)addrsize, family); + if (hp == NULL) + return (h_errno); + hostent_pack(hp, nvlout); + return (0); +} + +static int +dns_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct sockaddr_storage sast; + const void *sabin; + char *host, *serv; + size_t sabinsize, hostlen, servlen; + socklen_t salen; + int error, flags; + + if (!dns_allowed_type(limits, "ADDR2NAME") && + !dns_allowed_type(limits, "ADDR")) + return (NO_RECOVERY); + + error = 0; + host = serv = NULL; + memset(&sast, 0, sizeof(sast)); + + hostlen = (size_t)nvlist_get_number(nvlin, "hostlen"); + servlen = (size_t)nvlist_get_number(nvlin, "servlen"); + + if (hostlen > 0) { + host = calloc(1, hostlen); + if (host == NULL) { + error = EAI_MEMORY; + goto out; + } + } + if (servlen > 0) { + serv = calloc(1, servlen); + if (serv == NULL) { + error = EAI_MEMORY; + goto out; + } + } + + sabin = nvlist_get_binary(nvlin, "sa", &sabinsize); + if (sabinsize > sizeof(sast)) { + error = EAI_FAIL; + goto out; + } + + memcpy(&sast, sabin, sabinsize); + salen = (socklen_t)sabinsize; + + if ((sast.ss_family != AF_INET || + salen != sizeof(struct sockaddr_in)) && + (sast.ss_family != AF_INET6 || + salen != sizeof(struct sockaddr_in6))) { + error = EAI_FAIL; + goto out; + } + + if (!dns_allowed_family(limits, (int)sast.ss_family)) { + error = NO_RECOVERY; + goto out; + } + + flags = (int)nvlist_get_number(nvlin, "flags"); + + error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen, + serv, servlen, flags); + if (error != 0) + goto out; + + if (host != NULL) + nvlist_move_string(nvlout, "host", host); + if (serv != NULL) + nvlist_move_string(nvlout, "serv", serv); +out: + if (error != 0) { + free(host); + free(serv); + } + return (error); +} + +static nvlist_t * +addrinfo_pack(const struct addrinfo *ai) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags); + nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family); + nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype); + nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol); + nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen); + if (ai->ai_canonname != NULL) + nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname); + + return (nvl); +} + +static int +dns_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct addrinfo hints, *hintsp, *res, *cur; + const char *hostname, *servname; + char nvlname[64]; + nvlist_t *elem; + unsigned int ii; + int error, family, n; + + if (!dns_allowed_type(limits, "NAME2ADDR") && + !dns_allowed_type(limits, "NAME")) + return (NO_RECOVERY); + + hostname = dnvlist_get_string(nvlin, "hostname", NULL); + servname = dnvlist_get_string(nvlin, "servname", NULL); + if (nvlist_exists_number(nvlin, "hints.ai_flags")) { + hints.ai_flags = (int)nvlist_get_number(nvlin, + "hints.ai_flags"); + hints.ai_family = (int)nvlist_get_number(nvlin, + "hints.ai_family"); + hints.ai_socktype = (int)nvlist_get_number(nvlin, + "hints.ai_socktype"); + hints.ai_protocol = (int)nvlist_get_number(nvlin, + "hints.ai_protocol"); + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + hintsp = &hints; + family = hints.ai_family; + } else { + hintsp = NULL; + family = AF_UNSPEC; + } + + if (!dns_allowed_family(limits, family)) + return (NO_RECOVERY); + + error = getaddrinfo(hostname, servname, hintsp, &res); + if (error != 0) + goto out; + + for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) { + elem = addrinfo_pack(cur); + n = snprintf(nvlname, sizeof(nvlname), "res%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_move_nvlist(nvlout, nvlname, elem); + } + + freeaddrinfo(res); + error = 0; +out: + return (error); +} + +static bool +limit_has_entry(const nvlist_t *limits, const char *prefix) +{ + const char *name; + size_t prefixlen; + void *cookie; + + if (limits == NULL) + return (false); + + prefixlen = strlen(prefix); + + cookie = NULL; + while ((name = nvlist_next(limits, NULL, &cookie)) != NULL) { + if (strncmp(name, prefix, prefixlen) == 0) + return (true); + } + + return (false); +} + +static int +dns_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int nvtype; + bool hastype, hasfamily; + + hastype = false; + hasfamily = false; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) { + if (nvtype == NV_TYPE_STRING) { + const char *type; + + if (strncmp(name, "type", sizeof("type") - 1) != 0) + return (EINVAL); + type = nvlist_get_string(newlimits, name); + if (strcmp(type, "ADDR2NAME") != 0 && + strcmp(type, "NAME2ADDR") != 0 && + strcmp(type, "ADDR") != 0 && + strcmp(type, "NAME") != 0) { + return (EINVAL); + } + if (!dns_allowed_type(oldlimits, type)) + return (ENOTCAPABLE); + hastype = true; + } else if (nvtype == NV_TYPE_NUMBER) { + int family; + + if (strncmp(name, "family", sizeof("family") - 1) != 0) + return (EINVAL); + family = (int)nvlist_get_number(newlimits, name); + if (!dns_allowed_family(oldlimits, family)) + return (ENOTCAPABLE); + hasfamily = true; + } else { + return (EINVAL); + } + } + + /* + * If the new limit doesn't mention type or family we have to + * check if the current limit does have those. Missing type or + * family in the limit means that all types or families are + * allowed. + */ + if (!hastype) { + if (limit_has_entry(oldlimits, "type")) + return (ENOTCAPABLE); + } + if (!hasfamily) { + if (limit_has_entry(oldlimits, "family")) + return (ENOTCAPABLE); + } + + return (0); +} + +static int +dns_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (strcmp(cmd, "gethostbyname") == 0) + error = dns_gethostbyname(limits, nvlin, nvlout); + else if (strcmp(cmd, "gethostbyaddr") == 0) + error = dns_gethostbyaddr(limits, nvlin, nvlout); + else if (strcmp(cmd, "getnameinfo") == 0) + error = dns_getnameinfo(limits, nvlin, nvlout); + else if (strcmp(cmd, "getaddrinfo") == 0) + error = dns_getaddrinfo(limits, nvlin, nvlout); + else + error = NO_RECOVERY; + + return (error); +} + +CREATE_SERVICE("system.dns", dns_limit, dns_command, 0); diff --git a/lib/libcasper/services/cap_dns/cap_dns.h b/lib/libcasper/services/cap_dns/cap_dns.h new file mode 100644 index 000000000000..556cac1158d2 --- /dev/null +++ b/lib/libcasper/services/cap_dns/cap_dns.h @@ -0,0 +1,131 @@ +/*- + * Copyright (c) 2012 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_DNS_H_ +#define _CAP_DNS_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/cdefs.h> +#include <sys/socket.h> /* socklen_t */ + +/* + * Pull these in if we're just inlining calls to the underlying + * libc functions. + */ +#ifndef WITH_CASPER +#include <sys/types.h> +#include <netdb.h> +#endif /* WITH_CASPER */ + +struct addrinfo; +struct hostent; + +#ifdef WITH_CASPER +__BEGIN_DECLS + +struct hostent *cap_gethostbyname(cap_channel_t *chan, const char *name); +struct hostent *cap_gethostbyname2(cap_channel_t *chan, const char *name, + int type); +struct hostent *cap_gethostbyaddr(cap_channel_t *chan, const void *addr, + socklen_t len, int type); + +int cap_getaddrinfo(cap_channel_t *chan, const char *hostname, + const char *servname, const struct addrinfo *hints, struct addrinfo **res); +int cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, + socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, + int flags); + +int cap_dns_type_limit(cap_channel_t *chan, const char * const *types, + size_t ntypes); +int cap_dns_family_limit(cap_channel_t *chan, const int *families, + size_t nfamilies); + +__END_DECLS +#else + +static inline struct hostent * +cap_gethostbyname(cap_channel_t *chan __unused, const char *name) +{ + + return (gethostbyname(name)); +} + +static inline struct hostent * +cap_gethostbyname2(cap_channel_t *chan __unused, const char *name, int type) +{ + + return (gethostbyname2(name, type)); +} + +static inline struct hostent * +cap_gethostbyaddr(cap_channel_t *chan __unused, const void *addr, + socklen_t len, int type) +{ + + return (gethostbyaddr(addr, len, type)); +} + +static inline int cap_getaddrinfo(cap_channel_t *chan __unused, + const char *hostname, const char *servname, const struct addrinfo *hints, + struct addrinfo **res) +{ + + return (getaddrinfo(hostname, servname, hints, res)); +} + +static inline int cap_getnameinfo(cap_channel_t *chan __unused, + const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, + char *serv, size_t servlen, int flags) +{ + + return (getnameinfo(sa, salen, host, hostlen, serv, servlen, flags)); +} + +static inline int +cap_dns_type_limit(cap_channel_t *chan __unused, + const char * const *types __unused, + size_t ntypes __unused) +{ + + return (0); +} + +static inline int +cap_dns_family_limit(cap_channel_t *chan __unused, + const int *families __unused, + size_t nfamilies __unused) +{ + + return (0); +} +#endif /* WITH_CASPER */ + +#endif /* !_CAP_DNS_H_ */ diff --git a/lib/libcasper/services/cap_dns/tests/Makefile b/lib/libcasper/services/cap_dns/tests/Makefile new file mode 100644 index 000000000000..c7306581a803 --- /dev/null +++ b/lib/libcasper/services/cap_dns/tests/Makefile @@ -0,0 +1,12 @@ +.include <src.opts.mk> + +ATF_TESTS_C= dns_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_dns +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_dns/tests/Makefile.depend b/lib/libcasper/services/cap_dns/tests/Makefile.depend new file mode 100644 index 000000000000..560f3166bc47 --- /dev/null +++ b/lib/libcasper/services/cap_dns/tests/Makefile.depend @@ -0,0 +1,20 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/arpa \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/atf/libatf-c \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcasper/services/cap_dns \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_dns/tests/dns_test.c b/lib/libcasper/services/cap_dns/tests/dns_test.c new file mode 100644 index 000000000000..56d867e474f5 --- /dev/null +++ b/lib/libcasper/services/cap_dns/tests/dns_test.c @@ -0,0 +1,703 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/capsicum.h> +#include <sys/nv.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <casper/cap_dns.h> + +#include <atf-c.h> + +#define GETHOSTBYNAME 0x01 +#define GETHOSTBYNAME2_AF_INET 0x02 +#define GETHOSTBYNAME2_AF_INET6 0x04 +#define GETHOSTBYADDR_AF_INET 0x08 +#define GETHOSTBYADDR_AF_INET6 0x10 +#define GETADDRINFO_AF_UNSPEC 0x20 +#define GETADDRINFO_AF_INET 0x40 +#define GETADDRINFO_AF_INET6 0x80 + +static bool +addrinfo_compare(struct addrinfo *ai0, struct addrinfo *ai1) +{ + struct addrinfo *at0, *at1; + + if (ai0 == NULL && ai1 == NULL) + return (true); + if (ai0 == NULL || ai1 == NULL) + return (false); + + at0 = ai0; + at1 = ai1; + while (true) { + if ((at0->ai_flags == at1->ai_flags) && + (at0->ai_family == at1->ai_family) && + (at0->ai_socktype == at1->ai_socktype) && + (at0->ai_protocol == at1->ai_protocol) && + (at0->ai_addrlen == at1->ai_addrlen) && + (memcmp(at0->ai_addr, at1->ai_addr, + at0->ai_addrlen) == 0)) { + if (at0->ai_canonname != NULL && + at1->ai_canonname != NULL) { + if (strcmp(at0->ai_canonname, + at1->ai_canonname) != 0) { + return (false); + } + } + + if (at0->ai_canonname == NULL && + at1->ai_canonname != NULL) { + return (false); + } + if (at0->ai_canonname != NULL && + at1->ai_canonname == NULL) { + return (false); + } + + if (at0->ai_next == NULL && at1->ai_next == NULL) + return (true); + if (at0->ai_next == NULL || at1->ai_next == NULL) + return (false); + + at0 = at0->ai_next; + at1 = at1->ai_next; + } else { + return (false); + } + } + + /* NOTREACHED */ + fprintf(stderr, "Dead code reached in addrinfo_compare()\n"); + exit(1); +} + +static bool +hostent_aliases_compare(char **aliases0, char **aliases1) +{ + int i0, i1; + + if (aliases0 == NULL && aliases1 == NULL) + return (true); + if (aliases0 == NULL || aliases1 == NULL) + return (false); + + for (i0 = 0; aliases0[i0] != NULL; i0++) { + for (i1 = 0; aliases1[i1] != NULL; i1++) { + if (strcmp(aliases0[i0], aliases1[i1]) == 0) + break; + } + if (aliases1[i1] == NULL) + return (false); + } + + return (true); +} + +static bool +hostent_addr_list_compare(char **addr_list0, char **addr_list1, int length) +{ + int i0, i1; + + if (addr_list0 == NULL && addr_list1 == NULL) + return (true); + if (addr_list0 == NULL || addr_list1 == NULL) + return (false); + + for (i0 = 0; addr_list0[i0] != NULL; i0++) { + for (i1 = 0; addr_list1[i1] != NULL; i1++) { + if (memcmp(addr_list0[i0], addr_list1[i1], length) == 0) + break; + } + if (addr_list1[i1] == NULL) + return (false); + } + + return (true); +} + +static bool +hostent_compare(const struct hostent *hp0, const struct hostent *hp1) +{ + + if (hp0 == NULL && hp1 != NULL) + return (true); + + if (hp0 == NULL || hp1 == NULL) + return (false); + + if (hp0->h_name != NULL || hp1->h_name != NULL) { + if (hp0->h_name == NULL || hp1->h_name == NULL) + return (false); + if (strcmp(hp0->h_name, hp1->h_name) != 0) + return (false); + } + + if (!hostent_aliases_compare(hp0->h_aliases, hp1->h_aliases)) + return (false); + if (!hostent_aliases_compare(hp1->h_aliases, hp0->h_aliases)) + return (false); + + if (hp0->h_addrtype != hp1->h_addrtype) + return (false); + + if (hp0->h_length != hp1->h_length) + return (false); + + if (!hostent_addr_list_compare(hp0->h_addr_list, hp1->h_addr_list, + hp0->h_length)) { + return (false); + } + if (!hostent_addr_list_compare(hp1->h_addr_list, hp0->h_addr_list, + hp0->h_length)) { + return (false); + } + + return (true); +} + +static void +runtest(cap_channel_t *capdns, unsigned int expected) +{ + unsigned int result; + struct addrinfo *ais, *aic, hints, *hintsp; + struct hostent *hps, *hpc; + struct in_addr ip4; + struct in6_addr ip6; + int caperr, syserr; + + result = 0; + + hps = gethostbyname("example.com"); + if (hps == NULL) { + fprintf(stderr, "Unable to resolve %s IPv4.\n", "example.com"); + } else { + hpc = cap_gethostbyname(capdns, "example.com"); + if (hostent_compare(hps, hpc)) + result |= GETHOSTBYNAME; + } + + hps = gethostbyname2("example.com", AF_INET); + if (hps == NULL) { + fprintf(stderr, "Unable to resolve %s IPv4.\n", "example.com"); + } else { + hpc = cap_gethostbyname2(capdns, "example.com", AF_INET); + if (hostent_compare(hps, hpc)) + result |= GETHOSTBYNAME2_AF_INET; + } + + hps = gethostbyname2("example.com", AF_INET6); + if (hps == NULL) { + fprintf(stderr, "Unable to resolve %s IPv6.\n", "example.com"); + } else { + hpc = cap_gethostbyname2(capdns, "example.com", AF_INET6); + if (hostent_compare(hps, hpc)) + result |= GETHOSTBYNAME2_AF_INET6; + } + + hints.ai_flags = 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + + hintsp = &hints; + + syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais); + if (syserr != 0) { + fprintf(stderr, + "Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n", + gai_strerror(syserr)); + } else { + caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp, + &aic); + if (caperr == 0) { + if (addrinfo_compare(ais, aic)) + result |= GETADDRINFO_AF_UNSPEC; + freeaddrinfo(ais); + freeaddrinfo(aic); + } + } + + hints.ai_family = AF_INET; + syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais); + if (syserr != 0) { + fprintf(stderr, + "Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n", + gai_strerror(syserr)); + } else { + caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp, + &aic); + if (caperr == 0) { + if (addrinfo_compare(ais, aic)) + result |= GETADDRINFO_AF_INET; + freeaddrinfo(ais); + freeaddrinfo(aic); + } + } + + hints.ai_family = AF_INET6; + syserr = getaddrinfo("freebsd.org", "25", hintsp, &ais); + if (syserr != 0) { + fprintf(stderr, + "Unable to issue [system] getaddrinfo() for AF_UNSPEC: %s\n", + gai_strerror(syserr)); + } else { + caperr = cap_getaddrinfo(capdns, "freebsd.org", "25", hintsp, + &aic); + if (caperr == 0) { + if (addrinfo_compare(ais, aic)) + result |= GETADDRINFO_AF_INET6; + freeaddrinfo(ais); + freeaddrinfo(aic); + } + } + + /* XXX: hardcoded addresses for "google-public-dns-a.google.com". */ +#define GOOGLE_DNS_IPV4 "8.8.8.8" +#define GOOGLE_DNS_IPV6 "2001:4860:4860::8888" + + inet_pton(AF_INET, GOOGLE_DNS_IPV4, &ip4); + hps = gethostbyaddr(&ip4, sizeof(ip4), AF_INET); + if (hps == NULL) { + fprintf(stderr, "Unable to resolve %s.\n", GOOGLE_DNS_IPV4); + } else { + hpc = cap_gethostbyaddr(capdns, &ip4, sizeof(ip4), AF_INET); + if (hostent_compare(hps, hpc)) + result |= GETHOSTBYADDR_AF_INET; + } + + inet_pton(AF_INET6, GOOGLE_DNS_IPV6, &ip6); + hps = gethostbyaddr(&ip6, sizeof(ip6), AF_INET6); + if (hps == NULL) { + fprintf(stderr, "Unable to resolve %s.\n", GOOGLE_DNS_IPV6); + } else { + hpc = cap_gethostbyaddr(capdns, &ip6, sizeof(ip6), AF_INET6); + if (hostent_compare(hps, hpc)) { + caperr = h_errno; + result |= GETHOSTBYADDR_AF_INET6; + } + } + + ATF_REQUIRE_MSG(result == expected, + "expected 0x%x, got 0x%x", expected, result); +} + +static cap_channel_t * +cap_dns_init(void) +{ + cap_channel_t *capcas, *capdns; + + capcas = cap_init(); + ATF_REQUIRE(capcas != NULL); + + capdns = cap_service_open(capcas, "system.dns"); + ATF_REQUIRE(capdns != NULL); + + cap_close(capcas); + + return (capdns); +} + +ATF_TC(dns_no_limits); +ATF_TC_HEAD(dns_no_limits, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_no_limits, tc) +{ + cap_channel_t *capdns; + + capdns = cap_dns_init(); + + runtest(capdns, + (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 | + GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6 | + GETADDRINFO_AF_UNSPEC | GETADDRINFO_AF_INET | + GETADDRINFO_AF_INET6)); + + cap_close(capdns); +} + +ATF_TC(dns_all_limits); +ATF_TC_HEAD(dns_all_limits, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_all_limits, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET; + families[1] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, NULL, 0) == -1); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, NULL, 0) == -1); + + runtest(capdns, + (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 | + GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6 | + GETADDRINFO_AF_INET | GETADDRINFO_AF_INET6)); + + cap_close(capdns); +} + +ATF_TC(dns_name_limit); +ATF_TC_HEAD(dns_name_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_name_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0); + types[1] = "ADDR2NAME"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 2) == -1); + types[0] = "ADDR2NAME"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 1) == -1); + families[0] = AF_INET; + families[1] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + + runtest(capdns, + (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYNAME2_AF_INET6 | + GETADDRINFO_AF_INET | GETADDRINFO_AF_INET6)); + + cap_close(capdns); +} + +ATF_TC(dns_addr_limit); +ATF_TC_HEAD(dns_addr_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_addr_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0); + types[1] = "NAME2ADDR"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 2) == -1); + types[0] = "NAME2ADDR"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 1) == -1); + families[0] = AF_INET; + families[1] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + + runtest(capdns, + (GETHOSTBYADDR_AF_INET | GETHOSTBYADDR_AF_INET6)); + + cap_close(capdns); +} + +ATF_TC(dns_inet_limit); +ATF_TC_HEAD(dns_inet_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_inet_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0); + families[1] = AF_INET6; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 2) == -1); + families[0] = AF_INET6; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 1) == -1); + + runtest(capdns, + (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETHOSTBYADDR_AF_INET | + GETADDRINFO_AF_INET)); + + cap_close(capdns); +} + +ATF_TC(dns_inet6_limit); +ATF_TC_HEAD(dns_inet6_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_inet6_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0); + families[1] = AF_INET; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 2) == -1); + families[0] = AF_INET; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 1) == -1); + + runtest(capdns, + (GETHOSTBYNAME2_AF_INET6 | GETHOSTBYADDR_AF_INET6 | + GETADDRINFO_AF_INET6)); + + cap_close(capdns); +} + +ATF_TC(dns_name_inet_limit); +ATF_TC_HEAD(dns_name_inet_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_name_inet_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET; + families[1] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + types[0] = "NAME2ADDR"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0); + types[1] = "ADDR2NAME"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 2) == -1); + types[0] = "ADDR2NAME"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 1) == -1); + families[0] = AF_INET; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0); + families[1] = AF_INET6; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 2) == -1); + families[0] = AF_INET6; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 1) == -1); + + runtest(capdns, + (GETHOSTBYNAME | GETHOSTBYNAME2_AF_INET | GETADDRINFO_AF_INET)); + + cap_close(capdns); +} + +ATF_TC(dns_name_inet6_limit); +ATF_TC_HEAD(dns_name_inet6_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_name_inet6_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET6; + families[1] = AF_INET; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + types[0] = "NAME2ADDR"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0); + types[1] = "ADDR2NAME"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 2) == -1); + types[0] = "ADDR2NAME"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 1) == -1); + families[0] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0); + families[1] = AF_INET; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 2) == -1); + families[0] = AF_INET; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 1) == -1); + + runtest(capdns, + (GETHOSTBYNAME2_AF_INET6 | GETADDRINFO_AF_INET6)); + + cap_close(capdns); +} + +ATF_TC(dns_addr_inet_limit); +ATF_TC_HEAD(dns_addr_inet_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_addr_inet_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET; + families[1] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + types[0] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0); + types[1] = "NAME2ADDR"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 2) == -1); + types[0] = "NAME2ADDR"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 1) == -1); + families[0] = AF_INET; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0); + families[1] = AF_INET6; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 2) == -1); + families[0] = AF_INET6; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 1) == -1); + + runtest(capdns, GETHOSTBYADDR_AF_INET); + + cap_close(capdns); +} + +ATF_TC(dns_addr_inet6_limit); +ATF_TC_HEAD(dns_addr_inet6_limit, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(dns_addr_inet6_limit, tc) +{ + cap_channel_t *capdns; + const char *types[2]; + int families[2]; + + capdns = cap_dns_init(); + + types[0] = "NAME2ADDR"; + types[1] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 2) == 0); + families[0] = AF_INET6; + families[1] = AF_INET; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 2) == 0); + types[0] = "ADDR2NAME"; + ATF_REQUIRE(cap_dns_type_limit(capdns, types, 1) == 0); + types[1] = "NAME2ADDR"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 2) == -1); + types[0] = "NAME2ADDR"; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_type_limit(capdns, types, 1) == -1); + families[0] = AF_INET6; + ATF_REQUIRE(cap_dns_family_limit(capdns, families, 1) == 0); + families[1] = AF_INET; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 2) == -1); + families[0] = AF_INET; + ATF_REQUIRE_ERRNO(ENOTCAPABLE, + cap_dns_family_limit(capdns, families, 1) == -1); + + runtest(capdns, GETHOSTBYADDR_AF_INET6); + + cap_close(capdns); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, dns_no_limits); + ATF_TP_ADD_TC(tp, dns_all_limits); + ATF_TP_ADD_TC(tp, dns_name_limit); + ATF_TP_ADD_TC(tp, dns_addr_limit); + ATF_TP_ADD_TC(tp, dns_inet_limit); + ATF_TP_ADD_TC(tp, dns_inet6_limit); + ATF_TP_ADD_TC(tp, dns_name_inet_limit); + ATF_TP_ADD_TC(tp, dns_name_inet6_limit); + ATF_TP_ADD_TC(tp, dns_addr_inet_limit); + ATF_TP_ADD_TC(tp, dns_addr_inet6_limit); + + return atf_no_error(); +} diff --git a/lib/libcasper/services/cap_fileargs/Makefile b/lib/libcasper/services/cap_fileargs/Makefile new file mode 100644 index 000000000000..2c52d0887a48 --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/Makefile @@ -0,0 +1,38 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_fileargs + +SRCS= cap_fileargs.c +.endif + +INCS= cap_fileargs.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_fileargs.3 + +MLINKS+=cap_fileargs.3 libcap_fileargs.3 +MLINKS+=cap_fileargs.3 fileargs_cinit.3 +MLINKS+=cap_fileargs.3 fileargs_cinitnv.3 +MLINKS+=cap_fileargs.3 fileargs_fopen.3 +MLINKS+=cap_fileargs.3 fileargs_free.3 +MLINKS+=cap_fileargs.3 fileargs_init.3 +MLINKS+=cap_fileargs.3 fileargs_initnv.3 +MLINKS+=cap_fileargs.3 fileargs_lstat.3 +MLINKS+=cap_fileargs.3 fileargs_open.3 +MLINKS+=cap_fileargs.3 fileargs_realpath.3 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_fileargs/Makefile.depend b/lib/libcasper/services/cap_fileargs/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.3 b/lib/libcasper/services/cap_fileargs/cap_fileargs.3 new file mode 100644 index 000000000000..6a69fe7e1f4a --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.3 @@ -0,0 +1,300 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd August 8, 2025 +.Dt CAP_FILEARGS 3 +.Os +.Sh NAME +.Nm cap_fileargs , +.Nm fileargs_cinit , +.Nm fileargs_cinitnv , +.Nm fileargs_init , +.Nm fileargs_initnv , +.Nm fileargs_free , +.Nm fileargs_lstat , +.Nm fileargs_open , +.Nm fileargs_fopen +.Nd "library for handling files in capability mode" +.Sh SYNOPSIS +.Lb libcap_fileargs +.In sys/nv.h +.In libcasper.h +.In casper/cap_fileargs.h +.Ft "fileargs_t *" +.Fn fileargs_init "int argc" "char *argv[]" "int flags" "mode_t mode" "cap_rights_t *rightsp" "int operations" +.Ft "fileargs_t *" +.Fn fileargs_cinit "cap_channel_t *cas" "int argc" "char *argv[]" "int flags" "mode_t mode" "cap_rights_t *rightsp" "int operations" +.Ft "fileargs_t *" +.Fn fileargs_cinitnv "cap_channel_t *cas" "nvlist_t *limits" +.Ft "fileargs_t *" +.Fn fileargs_initnv "nvlist_t *limits" +.Ft "void" +.Fn fileargs_free "fileargs_t *fa" +.Ft "int" +.Fn fileargs_lstat "fileargs_t *fa" "const char *path" "struct stat *sb" +.Ft "int" +.Fn fileargs_open "fileargs_t *fa" "const char *name" +.Ft "FILE *" +.Fn fileargs_fopen "fileargs_t *fa" "const char *name" "const char *mode" +.Ft "char *" +.Fn fileargs_realpath "fileargs_t *fa" "const char *pathname" "char *reserved_path" +.Sh DESCRIPTION +The +.Nm +library is used to simplify Capsicumizing tools that are using file system. +The idea behind the library is that we pass the remaining arguments from +.Fa argv +(with count specified by +.Fa argc ) +which contains the list of files that should be opened by the program. +The library creates a service that will serve those files. +.Pp +The function +.Fn fileargs_init +creates a service to the +.Nm system.fileargs . +The +.Fa argv +contains a list of files that should be opened. +The argument can be set to +.Dv NULL +to create no service and prohibit all files from being opened. +The +.Fa argc +argument contains the number of files passed to the program. +The +.Fa flags +argument specifies whether files can be opened for execution, for reading, +and/or for writing. +The +.Fa mode +argument specifies the permissions to use when creating new files if the +.Dv O_CREAT +flag is set. +For more information about the +.Fa flags +and +.Fa mode +arguments, see +.Xr open 2 . +The +.Fa rightsp +argument specifies the capability rights that will be applied to restrict +access to the files. +For more information about capability rights, see +.Xr cap_rights_init 3 . +The +.Fa operations +argument specifies which operations are permitted when using +.Nm system.fileargs . +The following flags can be combined to form the +.Fa operations +value: +.Bl -ohang -offset indent +.It FA_OPEN +Allow +.Fn fileargs_open +and +.Fn fileargs_fopen . +.It FA_LSTAT +Allow +.Fn fileargs_lstat . +.It FA_REALPATH +Allow +.Fn fileargs_realpath . +.El +.Pp +The function +.Fn fileargs_cinit +behaves identically to +.Fn fileargs_init , +but requires an existing Casper connection to be passed as an argument. +.Pp +The functions +.Fn fileargs_initnv +and +.Fn fileargs_cinitnv +are equivalent to +.Fn fileargs_init +and +.Fn fileargs_cinit +respectively, but take their arguments in the form of an +.Xr nvlist 9 +structure. +See the +.Sx LIMITS +section for details on the expected argument types and values. +.Pp +The +.Fn fileargs_free +function closes the connection to the +.Nm system.fileargs +service and frees all associated data structures. +The function safely handles +.Dv NULL +arguments. +.Pp +The function +.Fn fileargs_lstat +provides the same functionality as +.Xr lstat 2 . +.Pp +The functions +.Fn fileargs_open +and +.Fn fileargs_fopen +behave identically to +.Xr open 2 +and +.Xr fopen 3 +respectively, but retrieve their arguments from the +.Va fileargs_t +structure. +.Pp +The function +.Fn fileargs_realpath +provides the same functionality as the standard C library function +.Xr realpath 3 , +resolving all symbolic links and references in a pathname. +.Pp +The following functions are reentrant but require synchronization for +thread safety: +.Fn fileargs_open , +.Fn fileargs_lstat , +.Fn fileargs_realpath , +.Fn fileargs_cinitnv , +.Fn fileargs_initnv , +and +.Fn fileargs_fopen . +Multiple threads can call these functions safely only if they use different +.Vt cap_channel_t +arguments or proper synchronization mechanisms. +.Sh LIMITS +This section describes the required and optional arguments that must be +passed to +.Fa system.fileargs +via the +.Fn fileargs_initnv +and +.Fn fileargs_cinitnv +functions using an +.Xr nvlist 9 +structure. +.Pp +The following arguments are required: +.Bl -ohang -offset indent +.It flags Pq Dv NV_TYPE_NUMBER +Specifies access permissions for opened files. +.It mode Pq Dv NV_TYPE_NUMBER +Required when the +.Dv O_CREATE +flag is set in +.Va flags . +Specifies the permissions to use when creating new files. +.It operations Pq Dv NV_TYPE_NUMBER +Specifies which operations are allowed for +.Fa system.fileargs . +See the description of the +.Va operations +argument in +.Fn fileargs_init +for possible values. +.El +.Pp +The following arguments are optional in the +.Xr nvlist 9 +structure: +.Bl -ohang -offset indent +.It cap_rights Pq Dv NV_TYPE_BINARY +The +.Va cap_rights +argument specifies the capability rights that will be applied to restrict +access to opened files. +.It filenames Pq Dv NV_TYPE_NULL +Multiple +.Dv NV_TYPE_NULL +elements can be provided, where each element's name represents a file +path that is allowed to be opened. +.El +.Sh EXAMPLES +.Bd -literal +int ch, fd, i; +cap_rights_t rights; +fileargs_t *fa; + +while ((ch = getopt(argc, argv, "h")) != -1) { + switch (ch) { + case 'h': + default: + usage(); + } +} + +argc -= optind; +argv += optind; + +/* Create capability to the system.fileargs service. */ +fa = fileargs_init(argc, argv, O_RDONLY, 0, + cap_rights_init(&rights, CAP_READ), FA_OPEN); +if (fa == NULL) + err(1, "unable to open system.fileargs service"); + +/* Enter capability mode sandbox. */ +if (cap_enter() < 0 && errno != ENOSYS) + err(1, "unable to enter capability mode"); + +/* Open files. */ +for (i = 0; i < argc; i++) { + fd = fileargs_open(fa, argv[i]); + if (fd < 0) + err(1, "unable to open file %s", argv[i]); + printf("File %s opened in capability mode\en", argv[i]); + close(fd); +} + +fileargs_free(fa); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr lstat 2 , +.Xr open 2 , +.Xr cap_rights_init 3 , +.Xr err 3 , +.Xr fopen 3 , +.Xr getopt 3 , +.Xr realpath 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm +service first appeared in +.Fx 10.3 . +.Sh AUTHORS +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org +.Sh BUGS +The +.Nm +service is considered experimental and should be thoroughly evaluated +for risks before deploying in production environments. diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.c b/lib/libcasper/services/cap_fileargs/cap_fileargs.c new file mode 100644 index 000000000000..233b47ede613 --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.c @@ -0,0 +1,737 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2018-2021 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/capsicum.h> +#include <sys/sysctl.h> +#include <sys/cnv.h> +#include <sys/dnv.h> +#include <sys/nv.h> +#include <sys/stat.h> + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_fileargs.h" + +#define CACHE_SIZE 128 + +#define FILEARGS_MAGIC 0xFA00FA00 + +struct fileargs { + uint32_t fa_magic; + nvlist_t *fa_cache; + cap_channel_t *fa_chann; + int fa_fdflags; +}; + +static int +fileargs_get_lstat_cache(fileargs_t *fa, const char *name, struct stat *sb) +{ + const nvlist_t *nvl; + size_t size; + const void *buf; + + assert(fa != NULL); + assert(fa->fa_magic == FILEARGS_MAGIC); + assert(name != NULL); + + if (fa->fa_cache == NULL) + return (-1); + + nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); + if (nvl == NULL) + return (-1); + + if (!nvlist_exists_binary(nvl, "stat")) { + return (-1); + } + + buf = nvlist_get_binary(nvl, "stat", &size); + assert(size == sizeof(*sb)); + memcpy(sb, buf, size); + + return (0); +} + +static int +fileargs_get_fd_cache(fileargs_t *fa, const char *name) +{ + int fd; + const nvlist_t *nvl; + nvlist_t *tnvl; + + assert(fa != NULL); + assert(fa->fa_magic == FILEARGS_MAGIC); + assert(name != NULL); + + if (fa->fa_cache == NULL) + return (-1); + + if ((fa->fa_fdflags & O_CREAT) != 0) + return (-1); + + nvl = dnvlist_get_nvlist(fa->fa_cache, name, NULL); + if (nvl == NULL) + return (-1); + + tnvl = nvlist_take_nvlist(fa->fa_cache, name); + + if (!nvlist_exists_descriptor(tnvl, "fd")) { + nvlist_destroy(tnvl); + return (-1); + } + + fd = nvlist_take_descriptor(tnvl, "fd"); + nvlist_destroy(tnvl); + + if ((fa->fa_fdflags & O_CLOEXEC) != O_CLOEXEC) { + if (fcntl(fd, F_SETFD, fa->fa_fdflags) == -1) { + close(fd); + return (-1); + } + } + + return (fd); +} + +static void +fileargs_set_cache(fileargs_t *fa, nvlist_t *nvl) +{ + + nvlist_destroy(fa->fa_cache); + fa->fa_cache = nvl; +} + +static nvlist_t* +fileargs_fetch(fileargs_t *fa, const char *name, const char *cmd) +{ + nvlist_t *nvl; + int serrno; + + assert(fa != NULL); + assert(name != NULL); + + nvl = nvlist_create(NV_FLAG_NO_UNIQUE); + nvlist_add_string(nvl, "cmd", cmd); + nvlist_add_string(nvl, "name", name); + + nvl = cap_xfer_nvlist(fa->fa_chann, nvl); + if (nvl == NULL) + return (NULL); + + if (nvlist_get_number(nvl, "error") != 0) { + serrno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + errno = serrno; + return (NULL); + } + + return (nvl); +} + +static nvlist_t * +fileargs_create_limit(int argc, const char * const *argv, int flags, + mode_t mode, cap_rights_t *rightsp, int operations) +{ + nvlist_t *limits; + int i; + + limits = nvlist_create(NV_FLAG_NO_UNIQUE); + if (limits == NULL) + return (NULL); + + nvlist_add_number(limits, "flags", flags); + nvlist_add_number(limits, "operations", operations); + if (rightsp != NULL) { + nvlist_add_binary(limits, "cap_rights", rightsp, + sizeof(*rightsp)); + } + if ((flags & O_CREAT) != 0) + nvlist_add_number(limits, "mode", (uint64_t)mode); + + for (i = 0; i < argc; i++) { + if (strlen(argv[i]) >= MAXPATHLEN) { + nvlist_destroy(limits); + errno = ENAMETOOLONG; + return (NULL); + } + nvlist_add_null(limits, argv[i]); + } + + return (limits); +} + +static fileargs_t * +fileargs_create(cap_channel_t *chan, int fdflags) +{ + fileargs_t *fa; + + fa = malloc(sizeof(*fa)); + if (fa != NULL) { + fa->fa_cache = NULL; + fa->fa_chann = chan; + fa->fa_fdflags = fdflags; + fa->fa_magic = FILEARGS_MAGIC; + } + + return (fa); +} + +fileargs_t * +fileargs_init(int argc, char *argv[], int flags, mode_t mode, + cap_rights_t *rightsp, int operations) +{ + nvlist_t *limits; + + if (argc <= 0 || argv == NULL) { + return (fileargs_create(NULL, 0)); + } + + limits = fileargs_create_limit(argc, (const char * const *)argv, flags, + mode, rightsp, operations); + if (limits == NULL) + return (NULL); + + return (fileargs_initnv(limits)); +} + +fileargs_t * +fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], int flags, + mode_t mode, cap_rights_t *rightsp, int operations) +{ + nvlist_t *limits; + + if (argc <= 0 || argv == NULL) { + return (fileargs_create(NULL, 0)); + } + + limits = fileargs_create_limit(argc, (const char * const *)argv, flags, + mode, rightsp, operations); + if (limits == NULL) + return (NULL); + + return (fileargs_cinitnv(cas, limits)); +} + +fileargs_t * +fileargs_initnv(nvlist_t *limits) +{ + cap_channel_t *cas; + fileargs_t *fa; + + if (limits == NULL) { + return (fileargs_create(NULL, 0)); + } + + cas = cap_init(); + if (cas == NULL) { + nvlist_destroy(limits); + return (NULL); + } + + fa = fileargs_cinitnv(cas, limits); + cap_close(cas); + + return (fa); +} + +fileargs_t * +fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits) +{ + cap_channel_t *chann; + fileargs_t *fa; + int flags, ret, serrno; + + assert(cas != NULL); + + if (limits == NULL) { + return (fileargs_create(NULL, 0)); + } + + chann = NULL; + fa = NULL; + + chann = cap_service_open(cas, "system.fileargs"); + if (chann == NULL) { + nvlist_destroy(limits); + return (NULL); + } + + flags = nvlist_get_number(limits, "flags"); + (void)nvlist_get_number(limits, "operations"); + + /* Limits are consumed no need to free them. */ + ret = cap_limit_set(chann, limits); + if (ret < 0) + goto out; + + fa = fileargs_create(chann, flags); + if (fa == NULL) + goto out; + + return (fa); +out: + serrno = errno; + if (chann != NULL) + cap_close(chann); + errno = serrno; + return (NULL); +} + +int +fileargs_open(fileargs_t *fa, const char *name) +{ + int fd; + nvlist_t *nvl; + char *cmd; + + assert(fa != NULL); + assert(fa->fa_magic == FILEARGS_MAGIC); + + if (name == NULL) { + errno = EINVAL; + return (-1); + } + + if (fa->fa_chann == NULL) { + errno = ENOTCAPABLE; + return (-1); + } + + fd = fileargs_get_fd_cache(fa, name); + if (fd != -1) + return (fd); + + nvl = fileargs_fetch(fa, name, "open"); + if (nvl == NULL) + return (-1); + + fd = nvlist_take_descriptor(nvl, "fd"); + cmd = nvlist_take_string(nvl, "cmd"); + if (strcmp(cmd, "cache") == 0) + fileargs_set_cache(fa, nvl); + else + nvlist_destroy(nvl); + free(cmd); + + return (fd); +} + +FILE * +fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) +{ + int fd; + + if ((fd = fileargs_open(fa, name)) < 0) { + return (NULL); + } + + return (fdopen(fd, mode)); +} + +int +fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb) +{ + nvlist_t *nvl; + const void *buf; + size_t size; + char *cmd; + + assert(fa != NULL); + assert(fa->fa_magic == FILEARGS_MAGIC); + + if (name == NULL) { + errno = EINVAL; + return (-1); + } + + if (sb == NULL) { + errno = EFAULT; + return (-1); + } + + if (fa->fa_chann == NULL) { + errno = ENOTCAPABLE; + return (-1); + } + + if (fileargs_get_lstat_cache(fa, name, sb) != -1) + return (0); + + nvl = fileargs_fetch(fa, name, "lstat"); + if (nvl == NULL) + return (-1); + + buf = nvlist_get_binary(nvl, "stat", &size); + assert(size == sizeof(*sb)); + memcpy(sb, buf, size); + + cmd = nvlist_take_string(nvl, "cmd"); + if (strcmp(cmd, "cache") == 0) + fileargs_set_cache(fa, nvl); + else + nvlist_destroy(nvl); + free(cmd); + + return (0); +} + +char * +fileargs_realpath(fileargs_t *fa, const char *pathname, char *reserved_path) +{ + nvlist_t *nvl; + char *ret; + + assert(fa != NULL); + assert(fa->fa_magic == FILEARGS_MAGIC); + + if (pathname == NULL) { + errno = EINVAL; + return (NULL); + } + + if (fa->fa_chann == NULL) { + errno = ENOTCAPABLE; + return (NULL); + } + + nvl = fileargs_fetch(fa, pathname, "realpath"); + if (nvl == NULL) + return (NULL); + + if (reserved_path != NULL) { + ret = reserved_path; + strcpy(reserved_path, + nvlist_get_string(nvl, "realpath")); + } else { + ret = nvlist_take_string(nvl, "realpath"); + } + nvlist_destroy(nvl); + + return (ret); +} + +void +fileargs_free(fileargs_t *fa) +{ + + if (fa == NULL) + return; + + assert(fa->fa_magic == FILEARGS_MAGIC); + + nvlist_destroy(fa->fa_cache); + if (fa->fa_chann != NULL) { + cap_close(fa->fa_chann); + } + explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); + free(fa); +} + +cap_channel_t * +fileargs_unwrap(fileargs_t *fa, int *flags) +{ + cap_channel_t *chan; + + if (fa == NULL) + return (NULL); + + assert(fa->fa_magic == FILEARGS_MAGIC); + + chan = fa->fa_chann; + if (flags != NULL) { + *flags = fa->fa_fdflags; + } + + nvlist_destroy(fa->fa_cache); + explicit_bzero(&fa->fa_magic, sizeof(fa->fa_magic)); + free(fa); + + return (chan); +} + +fileargs_t * +fileargs_wrap(cap_channel_t *chan, int fdflags) +{ + + if (chan == NULL) { + return (NULL); + } + + return (fileargs_create(chan, fdflags)); +} + +/* + * Service functions. + */ + +static const char *lastname; +static void *cacheposition; +static bool allcached; +static const cap_rights_t *caprightsp; +static int capflags; +static int allowed_operations; +static mode_t capmode; + +static int +open_file(const char *name) +{ + int fd, serrno; + + if ((capflags & O_CREAT) == 0) + fd = open(name, capflags); + else + fd = open(name, capflags, capmode); + if (fd < 0) + return (-1); + + if (caprightsp != NULL) { + if (cap_rights_limit(fd, caprightsp) < 0 && errno != ENOSYS) { + serrno = errno; + close(fd); + errno = serrno; + return (-1); + } + } + + return (fd); +} + +static void +fileargs_add_cache(nvlist_t *nvlout, const nvlist_t *limits, + const char *current_name) +{ + int type, i, fd; + void *cookie; + nvlist_t *new; + const char *fname; + struct stat sb; + + if ((capflags & O_CREAT) != 0) { + allcached = true; + return; + } + + cookie = cacheposition; + for (i = 0; i < CACHE_SIZE + 1; i++) { + fname = nvlist_next(limits, &type, &cookie); + if (fname == NULL) { + cacheposition = NULL; + lastname = NULL; + allcached = true; + return; + } + /* We doing that to catch next element name. */ + if (i == CACHE_SIZE) { + break; + } + + if (type != NV_TYPE_NULL) { + i--; + continue; + } + if (current_name != NULL && + strcmp(fname, current_name) == 0) { + current_name = NULL; + i--; + continue; + } + + new = nvlist_create(NV_FLAG_NO_UNIQUE); + if ((allowed_operations & FA_OPEN) != 0) { + fd = open_file(fname); + if (fd < 0) { + i--; + nvlist_destroy(new); + continue; + } + nvlist_move_descriptor(new, "fd", fd); + } + if ((allowed_operations & FA_LSTAT) != 0) { + if (lstat(fname, &sb) < 0) { + i--; + nvlist_destroy(new); + continue; + } + nvlist_add_binary(new, "stat", &sb, sizeof(sb)); + } + + nvlist_move_nvlist(nvlout, fname, new); + } + cacheposition = cookie; + lastname = fname; +} + +static bool +fileargs_allowed(const nvlist_t *limits, const nvlist_t *request, int operation) +{ + const char *name; + + if ((allowed_operations & operation) == 0) + return (false); + + name = dnvlist_get_string(request, "name", NULL); + if (name == NULL) + return (false); + + /* Fast path. */ + if (lastname != NULL && strcmp(name, lastname) == 0) + return (true); + + if (!nvlist_exists_null(limits, name)) + return (false); + + return (true); +} + +static int +fileargs_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + + if (oldlimits != NULL) + return (ENOTCAPABLE); + + capflags = (int)dnvlist_get_number(newlimits, "flags", 0); + allowed_operations = (int)dnvlist_get_number(newlimits, "operations", 0); + if ((capflags & O_CREAT) != 0) + capmode = (mode_t)nvlist_get_number(newlimits, "mode"); + else + capmode = 0; + + caprightsp = dnvlist_get_binary(newlimits, "cap_rights", NULL, NULL, 0); + + return (0); +} + +static int +fileargs_command_lstat(const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + const char *name; + struct stat sb; + + if (limits == NULL) + return (ENOTCAPABLE); + + if (!fileargs_allowed(limits, nvlin, FA_LSTAT)) + return (ENOTCAPABLE); + + name = nvlist_get_string(nvlin, "name"); + + error = lstat(name, &sb); + if (error < 0) + return (errno); + + if (!allcached && (lastname == NULL || + strcmp(name, lastname) == 0)) { + nvlist_add_string(nvlout, "cmd", "cache"); + fileargs_add_cache(nvlout, limits, name); + } else { + nvlist_add_string(nvlout, "cmd", "lstat"); + } + nvlist_add_binary(nvlout, "stat", &sb, sizeof(sb)); + return (0); +} + +static int +fileargs_command_realpath(const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + const char *pathname; + char *resolvedpath; + + if (limits == NULL) + return (ENOTCAPABLE); + + if (!fileargs_allowed(limits, nvlin, FA_REALPATH)) + return (ENOTCAPABLE); + + pathname = nvlist_get_string(nvlin, "name"); + resolvedpath = realpath(pathname, NULL); + if (resolvedpath == NULL) + return (errno); + + nvlist_move_string(nvlout, "realpath", resolvedpath); + return (0); +} + +static int +fileargs_command_open(const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int fd; + const char *name; + + if (limits == NULL) + return (ENOTCAPABLE); + + if (!fileargs_allowed(limits, nvlin, FA_OPEN)) + return (ENOTCAPABLE); + + name = nvlist_get_string(nvlin, "name"); + + fd = open_file(name); + if (fd < 0) + return (errno); + + if (!allcached && (lastname == NULL || + strcmp(name, lastname) == 0)) { + nvlist_add_string(nvlout, "cmd", "cache"); + fileargs_add_cache(nvlout, limits, name); + } else { + nvlist_add_string(nvlout, "cmd", "open"); + } + nvlist_move_descriptor(nvlout, "fd", fd); + return (0); +} + +static int +fileargs_command(const char *cmd, const nvlist_t *limits, + nvlist_t *nvlin, nvlist_t *nvlout) +{ + + if (strcmp(cmd, "open") == 0) + return (fileargs_command_open(limits, nvlin, nvlout)); + if (strcmp(cmd, "lstat") == 0) + return (fileargs_command_lstat(limits, nvlin, nvlout)); + if (strcmp(cmd, "realpath") == 0) + return (fileargs_command_realpath(limits, nvlin, nvlout)); + + return (EINVAL); +} + +CREATE_SERVICE("system.fileargs", fileargs_limit, fileargs_command, + CASPER_SERVICE_FD | CASPER_SERVICE_STDIO | CASPER_SERVICE_NO_UNIQ_LIMITS); diff --git a/lib/libcasper/services/cap_fileargs/cap_fileargs.h b/lib/libcasper/services/cap_fileargs/cap_fileargs.h new file mode 100644 index 000000000000..8207671d9753 --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/cap_fileargs.h @@ -0,0 +1,153 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _FILEARGS_H_ +#define _FILEARGS_H_ + +#include <sys/cdefs.h> +#include <sys/dnv.h> +#include <sys/nv.h> + +#include <stdbool.h> + +#define FA_OPEN 1 +#define FA_LSTAT 2 +#define FA_REALPATH 4 + +#ifdef WITH_CASPER +struct fileargs; +typedef struct fileargs fileargs_t; +struct stat; + +__BEGIN_DECLS + +fileargs_t *fileargs_init(int argc, char *argv[], int flags, mode_t mode, + cap_rights_t *rightsp, int operations); +fileargs_t *fileargs_cinit(cap_channel_t *cas, int argc, char *argv[], + int flags, mode_t mode, cap_rights_t *rightsp, int operations); +fileargs_t *fileargs_initnv(nvlist_t *limits); +fileargs_t *fileargs_cinitnv(cap_channel_t *cas, nvlist_t *limits); +int fileargs_lstat(fileargs_t *fa, const char *name, struct stat *sb); +int fileargs_open(fileargs_t *fa, const char *name); +char *fileargs_realpath(fileargs_t *fa, const char *pathname, + char *reserved_path); +void fileargs_free(fileargs_t *fa); +FILE *fileargs_fopen(fileargs_t *fa, const char *name, const char *mode); + +fileargs_t *fileargs_wrap(cap_channel_t *chan, int fdflags); +cap_channel_t *fileargs_unwrap(fileargs_t *fa, int *fdflags); + +__END_DECLS + +#else +typedef struct fileargs { + int fa_flags; + mode_t fa_mode; +} fileargs_t; + +static inline fileargs_t * +fileargs_init(int argc __unused, char *argv[] __unused, int flags, mode_t mode, + cap_rights_t *rightsp __unused, int operations __unused) { + fileargs_t *fa; + + fa = malloc(sizeof(*fa)); + if (fa != NULL) { + fa->fa_flags = flags; + fa->fa_mode = mode; + } + + return (fa); +} + +static inline fileargs_t * +fileargs_cinit(cap_channel_t *cas __unused, int argc, char *argv[], int flags, + mode_t mode, cap_rights_t *rightsp, int operations) +{ + + return (fileargs_init(argc, argv, flags, mode, rightsp, operations)); +} + +static inline fileargs_t * +fileargs_initnv(nvlist_t *limits) +{ + fileargs_t *fa; + + fa = fileargs_init(0, NULL, + nvlist_get_number(limits, "flags"), + dnvlist_get_number(limits, "mode", 0), + NULL, + nvlist_get_number(limits, "operations")); + nvlist_destroy(limits); + + return (fa); +} + +static inline fileargs_t * +fileargs_cinitnv(cap_channel_t *cas __unused, nvlist_t *limits) +{ + + return (fileargs_initnv(limits)); +} + +#define fileargs_lstat(fa, name, sb) \ + lstat(name, sb) +#define fileargs_open(fa, name) \ + open(name, fa->fa_flags, fa->fa_mode) +#define fileargs_realpath(fa, pathname, reserved_path) \ + realpath(pathname, reserved_path) + +static inline +FILE *fileargs_fopen(fileargs_t *fa, const char *name, const char *mode) +{ + (void) fa; + return (fopen(name, mode)); +} +#define fileargs_free(fa) (free(fa)) + +static inline fileargs_t * +fileargs_wrap(cap_channel_t *chan, int fdflags) +{ + + cap_close(chan); + return (fileargs_init(0, NULL, fdflags, 0, NULL, 0)); +} + +static inline cap_channel_t * +fileargs_unwrap(fileargs_t *fa, int *fdflags) +{ + + if (fdflags != NULL) { + *fdflags = fa->fa_flags; + } + fileargs_free(fa); + return (cap_init()); +} + +#endif + +#endif /* !_FILEARGS_H_ */ diff --git a/lib/libcasper/services/cap_fileargs/tests/Makefile b/lib/libcasper/services/cap_fileargs/tests/Makefile new file mode 100644 index 000000000000..02f02cceab8e --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/tests/Makefile @@ -0,0 +1,16 @@ +.include <src.opts.mk> + +ATF_TESTS_C= fileargs_test + +CFLAGS+= -I${SRCTOP}/tests + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_fileargs +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +WARNS?= 3 + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c new file mode 100644 index 000000000000..088edaaa294d --- /dev/null +++ b/lib/libcasper/services/cap_fileargs/tests/fileargs_test.c @@ -0,0 +1,777 @@ +/*- + * Copyright (c) 2021 Mariusz Zaborski <oshogbo@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/capsicum.h> +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> + +#include <atf-c.h> + +#include <libcasper.h> +#include <casper/cap_fileargs.h> + +#include "freebsd_test_suite/macros.h" + +#define MAX_FILES 200 + +static char *files[MAX_FILES]; +static int fds[MAX_FILES]; + +#define TEST_FILE "/etc/passwd" + +static void +check_capsicum(void) +{ + ATF_REQUIRE_FEATURE("security_capabilities"); + ATF_REQUIRE_FEATURE("security_capability_mode"); +} + +static void +prepare_files(size_t num, bool create) +{ + const char template[] = "testsfiles.XXXXXXXX"; + size_t i; + + for (i = 0; i < num; i++) { + files[i] = calloc(1, sizeof(template)); + ATF_REQUIRE(files[i] != NULL); + strncpy(files[i], template, sizeof(template) - 1); + + if (create) { + fds[i] = mkstemp(files[i]); + ATF_REQUIRE(fds[i] >= 0); + } else { + fds[i] = -1; + ATF_REQUIRE(mktemp(files[i]) != NULL); + } + } +} + +static void +clear_files(void) +{ + size_t i; + + + for (i = 0; files[i] != NULL; i++) { + unlink(files[i]); + free(files[i]); + if (fds[i] != -1) + close(fds[i]); + } +} + +static int +test_file_open(fileargs_t *fa, const char *file, int *fdp) +{ + int fd; + + fd = fileargs_open(fa, file); + if (fd < 0) + return (errno); + + if (fdp != NULL) { + *fdp = fd; + } + + return (0); +} + +static int +test_file_fopen(fileargs_t *fa, const char *file, const char *mode, + FILE **retfile) +{ + FILE *pfile; + + pfile = fileargs_fopen(fa, file, mode); + if (pfile == NULL) + return (errno); + + if (retfile != NULL) { + *retfile = pfile; + } + + return (0); +} + +static int +test_file_lstat(fileargs_t *fa, const char *file) +{ + struct stat fasb, origsb; + bool equals; + + if (fileargs_lstat(fa, file, &fasb) < 0) + return (errno); + + ATF_REQUIRE(lstat(file, &origsb) == 0); + + equals = true; + equals &= (origsb.st_dev == fasb.st_dev); + equals &= (origsb.st_ino == fasb.st_ino); + equals &= (origsb.st_nlink == fasb.st_nlink); + equals &= (origsb.st_flags == fasb.st_flags); + equals &= (memcmp(&origsb.st_ctim, &fasb.st_ctim, + sizeof(fasb.st_ctim)) == 0); + equals &= (memcmp(&origsb.st_birthtim, &fasb.st_birthtim, + sizeof(fasb.st_birthtim)) == 0); + if (!equals) { + return (EINVAL); + } + + return (0); +} + +static int +test_file_realpath_static(fileargs_t *fa, const char *file) +{ + char fapath[PATH_MAX], origpath[PATH_MAX]; + + if (fileargs_realpath(fa, file, fapath) == NULL) + return (errno); + + ATF_REQUIRE(realpath(file, origpath) != NULL); + + if (strcmp(fapath, origpath) != 0) + return (EINVAL); + + return (0); +} + +static int +test_file_realpath_alloc(fileargs_t *fa, const char *file) +{ + char *fapath, *origpath; + int serrno; + + fapath = fileargs_realpath(fa, file, NULL); + if (fapath == NULL) + return (errno); + + origpath = realpath(file, NULL); + ATF_REQUIRE(origpath != NULL); + + serrno = 0; + if (strcmp(fapath, origpath) != 0) + serrno = EINVAL; + + free(fapath); + free(origpath); + + return (serrno); +} + +static int +test_file_realpath(fileargs_t *fa, const char *file) +{ + int serrno; + + serrno = test_file_realpath_static(fa, file); + if (serrno != 0) + return serrno; + + return (test_file_realpath_alloc(fa, file)); +} + +static int +test_file_mode(int fd, int mode) +{ + int flags; + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return (errno); + + if ((flags & O_ACCMODE) != mode) + return (errno); + + return (0); +} + +static bool +test_file_cap(int fd, cap_rights_t *rights) +{ + cap_rights_t fdrights; + + ATF_REQUIRE(cap_rights_get(fd, &fdrights) == 0); + + return (cap_rights_contains(&fdrights, rights)); +} + +static int +test_file_write(int fd) +{ + char buf; + + buf = 't'; + if (write(fd, &buf, sizeof(buf)) != sizeof(buf)) { + return (errno); + } + + return (0); +} + +static int +test_file_read(int fd) +{ + char buf; + + if (read(fd, &buf, sizeof(buf)) < 0) { + return (errno); + } + + return (0); +} + +static int +test_file_fwrite(FILE *pfile) +{ + char buf; + + buf = 't'; + if (fwrite(&buf, sizeof(buf), 1, pfile) != sizeof(buf)) + return (errno); + + return (0); +} + +static int +test_file_fread(FILE *pfile) +{ + char buf; + int ret, serrno; + + errno = 0; + ret = fread(&buf, sizeof(buf), 1, pfile); + serrno = errno; + if (ret < 0) { + return (serrno); + } else if (ret == 0 && feof(pfile) == 0) { + return (serrno != 0 ? serrno : EINVAL); + } + + return (0); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_read); +ATF_TC_HEAD(fileargs__open_read, tc) {} +ATF_TC_BODY(fileargs__open_read, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ, CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_read, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_write); +ATF_TC_HEAD(fileargs__open_write, tc) {} +ATF_TC_BODY(fileargs__open_write, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_WRITE, CAP_FCNTL); + cap_rights_init(&norights, CAP_READ); + fa = fileargs_init(MAX_FILES, files, O_WRONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_mode(fd, O_WRONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_write(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_read(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_write, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_create); +ATF_TC_HEAD(fileargs__open_create, tc) {} +ATF_TC_BODY(fileargs__open_create, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, false); + + cap_rights_init(&rights, CAP_WRITE, CAP_FCNTL, CAP_READ); + cap_rights_init(&norights, CAP_FCHMOD); + fa = fileargs_init(MAX_FILES, files, O_RDWR | O_CREAT, 666, + &rights, FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + + ATF_REQUIRE(test_file_mode(fd, O_RDWR) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_write(fd) == 0); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_create, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_with_casper); +ATF_TC_HEAD(fileargs__open_with_casper, tc) {} +ATF_TC_BODY(fileargs__open_with_casper, tc) +{ + cap_channel_t *capcas; + cap_rights_t rights; + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + capcas = cap_init(); + ATF_REQUIRE(capcas != NULL); + + cap_rights_init(&rights, CAP_READ); + fa = fileargs_cinit(capcas, MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_with_casper, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__fopen_read); +ATF_TC_HEAD(fileargs__fopen_read, tc) {} +ATF_TC_BODY(fileargs__fopen_read, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + FILE *pfile; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ, CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We fopen file twice to check if we can. */ + ATF_REQUIRE(test_file_fopen(fa, files[i], "r", &pfile) == 0); + ATF_REQUIRE(fclose(pfile) == 0); + + ATF_REQUIRE(test_file_fopen(fa, files[i], "r", &pfile) == 0); + fd = fileno(pfile); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_fread(pfile) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "r", NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_fwrite(pfile) == EBADF); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(fclose(pfile) == 0); + } +} +ATF_TC_CLEANUP(fileargs__fopen_read, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__fopen_write); +ATF_TC_HEAD(fileargs__fopen_write, tc) {} +ATF_TC_BODY(fileargs__fopen_write, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + FILE *pfile; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_WRITE, CAP_FCNTL); + cap_rights_init(&norights, CAP_READ); + fa = fileargs_init(MAX_FILES, files, O_WRONLY, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We fopen file twice to check if we can. */ + ATF_REQUIRE(test_file_fopen(fa, files[i], "w", &pfile) == 0); + ATF_REQUIRE(fclose(pfile) == 0); + + ATF_REQUIRE(test_file_fopen(fa, files[i], "w", &pfile) == 0); + fd = fileno(pfile); + ATF_REQUIRE(test_file_mode(fd, O_WRONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_fwrite(pfile) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w", NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_fread(pfile) == EBADF); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(fclose(pfile) == 0); + } +} +ATF_TC_CLEANUP(fileargs__fopen_write, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__fopen_create); +ATF_TC_HEAD(fileargs__fopen_create, tc) {} +ATF_TC_BODY(fileargs__fopen_create, tc) +{ + cap_rights_t rights; + fileargs_t *fa; + size_t i; + FILE *pfile; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, false); + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_FCNTL); + fa = fileargs_init(MAX_FILES, files, O_RDWR | O_CREAT, 0, &rights, + FA_OPEN); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We fopen file twice to check if we can. */ + ATF_REQUIRE(test_file_fopen(fa, files[i], "w+", &pfile) == 0); + fd = fileno(pfile); + ATF_REQUIRE(test_file_mode(fd, O_RDWR) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_fwrite(pfile) == 0); + ATF_REQUIRE(test_file_fread(pfile) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_fopen(fa, TEST_FILE, "w+", NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(fclose(pfile) == 0); + } +} +ATF_TC_CLEANUP(fileargs__fopen_create, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__lstat); +ATF_TC_HEAD(fileargs__lstat, tc) {} +ATF_TC_BODY(fileargs__lstat, tc) +{ + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + fa = fileargs_init(MAX_FILES, files, 0, 0, NULL, FA_LSTAT); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + } +} +ATF_TC_CLEANUP(fileargs__lstat, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__realpath); +ATF_TC_HEAD(fileargs__realpath, tc) {} +ATF_TC_BODY(fileargs__realpath, tc) +{ + fileargs_t *fa; + size_t i; + int fd; + + prepare_files(MAX_FILES, true); + + fa = fileargs_init(MAX_FILES, files, 0, 0, NULL, FA_REALPATH); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, TEST_FILE) == ENOTCAPABLE); + ATF_REQUIRE(test_file_open(fa, TEST_FILE, &fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + } +} +ATF_TC_CLEANUP(fileargs__realpath, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_lstat); +ATF_TC_HEAD(fileargs__open_lstat, tc) {} +ATF_TC_BODY(fileargs__open_lstat, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ, CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN | FA_LSTAT); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0); + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_lstat(fa, files[i]) == 0); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == ENOTCAPABLE); + ATF_REQUIRE(test_file_realpath(fa, TEST_FILE) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_lstat, tc) +{ + clear_files(); +} + +ATF_TC_WITH_CLEANUP(fileargs__open_realpath); +ATF_TC_HEAD(fileargs__open_realpath, tc) {} +ATF_TC_BODY(fileargs__open_realpath, tc) +{ + cap_rights_t rights, norights; + fileargs_t *fa; + size_t i; + int fd; + + check_capsicum(); + + prepare_files(MAX_FILES, true); + + cap_rights_init(&rights, CAP_READ, CAP_FCNTL); + cap_rights_init(&norights, CAP_WRITE); + fa = fileargs_init(MAX_FILES, files, O_RDONLY, 0, &rights, + FA_OPEN | FA_REALPATH); + ATF_REQUIRE(fa != NULL); + + for (i = 0; i < MAX_FILES; i++) { + /* ALLOWED */ + /* We open file twice to check if we can. */ + ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0); + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(close(fd) == 0); + + ATF_REQUIRE(test_file_open(fa, files[i], &fd) == 0); + ATF_REQUIRE(test_file_realpath(fa, files[i]) == 0); + ATF_REQUIRE(test_file_mode(fd, O_RDONLY) == 0); + ATF_REQUIRE(test_file_cap(fd, &rights) == true); + ATF_REQUIRE(test_file_read(fd) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_file_open(fa, TEST_FILE, NULL) == ENOTCAPABLE); + ATF_REQUIRE(test_file_cap(fd, &norights) == false); + ATF_REQUIRE(test_file_write(fd) == ENOTCAPABLE); + ATF_REQUIRE(test_file_lstat(fa, files[i]) == ENOTCAPABLE); + + /* CLOSE */ + ATF_REQUIRE(close(fd) == 0); + } +} +ATF_TC_CLEANUP(fileargs__open_realpath, tc) +{ + clear_files(); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, fileargs__open_create); + ATF_TP_ADD_TC(tp, fileargs__open_read); + ATF_TP_ADD_TC(tp, fileargs__open_write); + ATF_TP_ADD_TC(tp, fileargs__open_with_casper); + + ATF_TP_ADD_TC(tp, fileargs__fopen_create); + ATF_TP_ADD_TC(tp, fileargs__fopen_read); + ATF_TP_ADD_TC(tp, fileargs__fopen_write); + + ATF_TP_ADD_TC(tp, fileargs__lstat); + + ATF_TP_ADD_TC(tp, fileargs__realpath); + + ATF_TP_ADD_TC(tp, fileargs__open_lstat); + ATF_TP_ADD_TC(tp, fileargs__open_realpath); + + return (atf_no_error()); +} diff --git a/lib/libcasper/services/cap_grp/Makefile b/lib/libcasper/services/cap_grp/Makefile new file mode 100644 index 000000000000..a921dfa87e7c --- /dev/null +++ b/lib/libcasper/services/cap_grp/Makefile @@ -0,0 +1,44 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_grp + +SRCS= cap_grp.c +.endif + +INCS= cap_grp.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_grp.3 + +MLINKS+=cap_grp.3 libcap_grp.3 +MLINKS+=cap_grp.3 cap_getgrent.3 +MLINKS+=cap_grp.3 cap_getgrnam.3 +MLINKS+=cap_grp.3 cap_getgrgid.3 +MLINKS+=cap_grp.3 cap_getgrent_r.3 +MLINKS+=cap_grp.3 cap_getgrnam_r.3 +MLINKS+=cap_grp.3 cap_getgrgid_r.3 +MLINKS+=cap_grp.3 cap_setgroupent.3 +MLINKS+=cap_grp.3 cap_setgrent.3 +MLINKS+=cap_grp.3 cap_endgrent.3 +MLINKS+=cap_grp.3 cap_grp_limit_cmds.3 +MLINKS+=cap_grp.3 cap_grp_limit_fields.3 +MLINKS+=cap_grp.3 cap_grp_limit_groups.3 + +.include <bsd.lib.mk> + +# GCC 13 complains incorrectly about free after failed realloc: GCC bug #110501 +CFLAGS.cap_grp.c+= ${NO_WUSE_AFTER_FREE} diff --git a/lib/libcasper/services/cap_grp/Makefile.depend b/lib/libcasper/services/cap_grp/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_grp/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_grp/cap_grp.3 b/lib/libcasper/services/cap_grp/cap_grp.3 new file mode 100644 index 000000000000..578d8edffbfa --- /dev/null +++ b/lib/libcasper/services/cap_grp/cap_grp.3 @@ -0,0 +1,236 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt CAP_GRP 3 +.Os +.Sh NAME +.Nm cap_getgrent , +.Nm cap_getgrnam , +.Nm cap_getgrgid , +.Nm cap_getgrent_r , +.Nm cap_getgrnam_r , +.Nm cap_getgrgid_r , +.Nm cap_setgroupent , +.Nm cap_setgrent , +.Nm cap_endgrent , +.Nm cap_grp_limit_cmds , +.Nm cap_grp_limit_fields , +.Nm cap_grp_limit_groups +.Nd "library for group database operations in capability mode" +.Sh LIBRARY +.Lb libcap_grp +.Sh SYNOPSIS +.In sys/nv.h +.In libcasper.h +.In casper/cap_grp.h +.Ft "struct group *" +.Fn cap_getgrent "cap_channel_t *chan" +.Ft "struct group *" +.Fn cap_getgrnam "cap_channel_t *chan" "const char *name" +.Ft "struct group *" +.Fn cap_getgrgid "cap_channel_t *chan" "gid_t gid" +.Ft "int" +.Fn cap_getgrent_r "cap_channel_t *chan" "struct group *grp" "char *buffer" "size_t bufsize" "struct group **result" +.Ft "int" +.Fn cap_getgrnam_r "cap_channel_t *chan" "const char *name" "struct group *grp" "char *buffer" "size_t bufsize" "struct group **result" +.Ft int +.Fn cap_getgrgid_r "cap_channel_t *chan" "gid_t gid" "struct group *grp" "char *buffer" "size_t bufsize" "struct group **result" +.Ft int +.Fn cap_setgroupent "cap_channel_t *chan" "int stayopen" +.Ft int +.Fn cap_setgrent "cap_channel_t *chan" +.Ft void +.Fn cap_endgrent "cap_channel_t *chan" +.Ft int +.Fn cap_grp_limit_cmds "cap_channel_t *chan" "const char * const *cmds" "size_t ncmds" +.Ft int +.Fn cap_grp_limit_fields "cap_channel_t *chan" "const char * const *fields" "size_t nfields" +.Ft int +.Fn cap_grp_limit_groups "cap_channel_t *chan" "const char * const *names" "size_t nnames" "const gid_t *gids" "size_t ngids" +.Sh DESCRIPTION +The functions +.Fn cap_getgrent , +.Fn cap_getgrnam , +.Fn cap_getgrgid , +.Fn cap_getgrent_r , +.Fn cap_getgrnam_r , +.Fn cap_getgrgid_r , +.Fn cap_setgroupent , +.Fn cap_setgrent , +and +.Fn cap_endgrent +are respectively equivalent to +.Xr getgrent 3 , +.Xr getgrnam 3 , +.Xr getgrgid 3 , +.Xr getgrent_r 3 , +.Xr getgrnam_r 3 , +.Xr getgrgid_r 3 , +.Xr setgroupent 3 , +.Xr setgrent 3 , +and +.Xr endgrent 3 +except that the connection to the +.Nm system.grp +service needs to be provided. +.Pp +The +.Fn cap_grp_limit_cmds +function limits the functions allowed in the service. +The +.Fa cmds +variable can be set to +.Dv getgrent , +.Dv getgrnam , +.Dv getgrgid , +.Dv getgrent_r , +.Dv getgrnam_r , +.Dv getgrgid_r , +.Dv setgroupent , +.Dv setgrent , +or +.Dv endgrent +which will allow to use the function associated with the name. +The +.Fa ncmds +variable contains the number of +.Fa cmds +provided. +.Pp +The +.Fn cap_grp_limit_fields +function allows limit fields returned in the structure +.Vt group . +The +.Fa fields +variable can be set to +.Dv gr_name +.Dv gr_passwd +.Dv gr_gid +or +.Dv gr_mem . +The field which was set as the limit will be returned, while the rest of the +values not set this way will have default values. +The +.Fa nfields +variable contains the number of +.Fa fields +provided. +.Pp +The +.Fn cap_grp_limit_groups +function allows to limit access to groups. +The +.Fa names +variable allows to limit groups by name and the +.Fa gids +variable by the group number. +The +.Fa nnames +and +.Fa ngids +variables provide numbers of limited names and gids. +.Pp +All of these functions are reentrant but not thread-safe. +That is, they may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh EXAMPLES +The following example first opens a capability to casper and then uses this +capability to create the +.Nm system.grp +casper service and uses it to get a group name. +.Bd -literal +cap_channel_t *capcas, *capgrp; +const char *cmds[] = { "getgrgid" }; +const char *fields[] = { "gr_name" }; +const gid_t gid[] = { 1 }; +struct group *group; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Enter capability mode sandbox. */ +if (cap_enter() < 0 && errno != ENOSYS) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.grp service. */ +capgrp = cap_service_open(capcas, "system.grp"); +if (capgrp == NULL) + err(1, "Unable to open system.grp service"); + +/* Close Casper capability, we don't need it anymore. */ +cap_close(capcas); + +/* Limit service to one single function. */ +if (cap_grp_limit_cmds(capgrp, cmds, nitems(cmds))) + err(1, "Unable to limit access to system.grp service"); + +/* Limit service to one field as we only need name of the group. */ +if (cap_grp_limit_fields(capgrp, fields, nitems(fields))) + err(1, "Unable to limit access to system.grp service"); + +/* Limit service to one gid. */ +if (cap_grp_limit_groups(capgrp, NULL, 0, gid, nitems(gid))) + err(1, "Unable to limit access to system.grp service"); + +group = cap_getgrgid(capgrp, gid[0]); +if (group == NULL) + err(1, "Unable to get name of group"); + +printf("GID %d is associated with name %s.\\n", gid[0], group->gr_name); + +cap_close(capgrp); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr endgrent 3 , +.Xr err 3 , +.Xr getgrent 3 , +.Xr getgrent_r 3 , +.Xr getgrgid 3 , +.Xr getgrgid_r 3 , +.Xr getgrnam 3 , +.Xr getgrnam_r 3 , +.Xr setgrent 3 , +.Xr setgroupent 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm cap_grp +service first appeared in +.Fx 10.3 . +.Sh AUTHORS +The +.Nm cap_grp +service was implemented by +.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net +under sponsorship from the FreeBSD Foundation. +.Pp +This manual page was written by +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org . diff --git a/lib/libcasper/services/cap_grp/cap_grp.c b/lib/libcasper/services/cap_grp/cap_grp.c new file mode 100644 index 000000000000..025ce00adf56 --- /dev/null +++ b/lib/libcasper/services/cap_grp/cap_grp.c @@ -0,0 +1,788 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/dnv.h> +#include <sys/nv.h> +#include <sys/param.h> + +#include <assert.h> +#include <errno.h> +#include <grp.h> +#include <stdlib.h> +#include <string.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_grp.h" + +static struct group ggrp; +static char *gbuffer; +static size_t gbufsize; + +static int +group_resize(void) +{ + char *buf; + + if (gbufsize == 0) + gbufsize = 1024; + else + gbufsize *= 2; + + buf = gbuffer; + gbuffer = realloc(buf, gbufsize); + if (gbuffer == NULL) { + free(buf); + gbufsize = 0; + return (ENOMEM); + } + memset(gbuffer, 0, gbufsize); + + return (0); +} + +static int +group_unpack_string(const nvlist_t *nvl, const char *fieldname, char **fieldp, + char **bufferp, size_t *bufsizep) +{ + const char *str; + size_t len; + + str = nvlist_get_string(nvl, fieldname); + len = strlcpy(*bufferp, str, *bufsizep); + if (len >= *bufsizep) + return (ERANGE); + *fieldp = *bufferp; + *bufferp += len + 1; + *bufsizep -= len + 1; + + return (0); +} + +static int +group_unpack_members(const nvlist_t *nvl, char ***fieldp, char **bufferp, + size_t *bufsizep) +{ + const char *mem; + char **outstrs, *str, nvlname[64]; + size_t nmem, datasize, strsize; + unsigned int ii; + int n; + + if (!nvlist_exists_number(nvl, "gr_nmem")) { + datasize = _ALIGNBYTES + sizeof(char *); + if (datasize >= *bufsizep) + return (ERANGE); + outstrs = (char **)_ALIGN(*bufferp); + outstrs[0] = NULL; + *fieldp = outstrs; + *bufferp += datasize; + *bufsizep -= datasize; + return (0); + } + + nmem = (size_t)nvlist_get_number(nvl, "gr_nmem"); + datasize = _ALIGNBYTES + sizeof(char *) * (nmem + 1); + for (ii = 0; ii < nmem; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + mem = dnvlist_get_string(nvl, nvlname, NULL); + if (mem == NULL) + return (EINVAL); + datasize += strlen(mem) + 1; + } + + if (datasize >= *bufsizep) + return (ERANGE); + + outstrs = (char **)_ALIGN(*bufferp); + str = (char *)outstrs + sizeof(char *) * (nmem + 1); + for (ii = 0; ii < nmem; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + mem = nvlist_get_string(nvl, nvlname); + strsize = strlen(mem) + 1; + memcpy(str, mem, strsize); + outstrs[ii] = str; + str += strsize; + } + assert(ii == nmem); + outstrs[ii] = NULL; + + *fieldp = outstrs; + *bufferp += datasize; + *bufsizep -= datasize; + + return (0); +} + +static int +group_unpack(const nvlist_t *nvl, struct group *grp, char *buffer, + size_t bufsize) +{ + int error; + + if (!nvlist_exists_string(nvl, "gr_name")) + return (EINVAL); + + explicit_bzero(grp, sizeof(*grp)); + + error = group_unpack_string(nvl, "gr_name", &grp->gr_name, &buffer, + &bufsize); + if (error != 0) + return (error); + error = group_unpack_string(nvl, "gr_passwd", &grp->gr_passwd, &buffer, + &bufsize); + if (error != 0) + return (error); + grp->gr_gid = (gid_t)nvlist_get_number(nvl, "gr_gid"); + error = group_unpack_members(nvl, &grp->gr_mem, &buffer, &bufsize); + if (error != 0) + return (error); + + return (0); +} + +static int +cap_getgrcommon_r(cap_channel_t *chan, const char *cmd, const char *name, + gid_t gid, struct group *grp, char *buffer, size_t bufsize, + struct group **result) +{ + nvlist_t *nvl; + bool getgr_r; + int error; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", cmd); + if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0) { + /* Add nothing. */ + } else if (strcmp(cmd, "getgrnam") == 0 || + strcmp(cmd, "getgrnam_r") == 0) { + nvlist_add_string(nvl, "name", name); + } else if (strcmp(cmd, "getgrgid") == 0 || + strcmp(cmd, "getgrgid_r") == 0) { + nvlist_add_number(nvl, "gid", (uint64_t)gid); + } else { + abort(); + } + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + assert(errno != 0); + *result = NULL; + return (errno); + } + error = (int)nvlist_get_number(nvl, "error"); + if (error != 0) { + nvlist_destroy(nvl); + *result = NULL; + return (error); + } + + if (!nvlist_exists_string(nvl, "gr_name")) { + /* Not found. */ + nvlist_destroy(nvl); + *result = NULL; + return (0); + } + + getgr_r = (strcmp(cmd, "getgrent_r") == 0 || + strcmp(cmd, "getgrnam_r") == 0 || strcmp(cmd, "getgrgid_r") == 0); + + for (;;) { + error = group_unpack(nvl, grp, buffer, bufsize); + if (getgr_r || error != ERANGE) + break; + assert(buffer == gbuffer); + assert(bufsize == gbufsize); + error = group_resize(); + if (error != 0) + break; + /* Update pointers after resize. */ + buffer = gbuffer; + bufsize = gbufsize; + } + + nvlist_destroy(nvl); + + if (error == 0) + *result = grp; + else + *result = NULL; + + return (error); +} + +static struct group * +cap_getgrcommon(cap_channel_t *chan, const char *cmd, const char *name, + gid_t gid) +{ + struct group *result; + int error, serrno; + + serrno = errno; + + error = cap_getgrcommon_r(chan, cmd, name, gid, &ggrp, gbuffer, + gbufsize, &result); + if (error != 0) { + errno = error; + return (NULL); + } + + errno = serrno; + + return (result); +} + +struct group * +cap_getgrent(cap_channel_t *chan) +{ + + return (cap_getgrcommon(chan, "getgrent", NULL, 0)); +} + +struct group * +cap_getgrnam(cap_channel_t *chan, const char *name) +{ + + return (cap_getgrcommon(chan, "getgrnam", name, 0)); +} + +struct group * +cap_getgrgid(cap_channel_t *chan, gid_t gid) +{ + + return (cap_getgrcommon(chan, "getgrgid", NULL, gid)); +} + +int +cap_getgrent_r(cap_channel_t *chan, struct group *grp, char *buffer, + size_t bufsize, struct group **result) +{ + + return (cap_getgrcommon_r(chan, "getgrent_r", NULL, 0, grp, buffer, + bufsize, result)); +} + +int +cap_getgrnam_r(cap_channel_t *chan, const char *name, struct group *grp, + char *buffer, size_t bufsize, struct group **result) +{ + + return (cap_getgrcommon_r(chan, "getgrnam_r", name, 0, grp, buffer, + bufsize, result)); +} + +int +cap_getgrgid_r(cap_channel_t *chan, gid_t gid, struct group *grp, char *buffer, + size_t bufsize, struct group **result) +{ + + return (cap_getgrcommon_r(chan, "getgrgid_r", NULL, gid, grp, buffer, + bufsize, result)); +} + +int +cap_setgroupent(cap_channel_t *chan, int stayopen) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "setgroupent"); + nvlist_add_bool(nvl, "stayopen", stayopen != 0); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (0); + if (nvlist_get_number(nvl, "error") != 0) { + errno = nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (0); + } + nvlist_destroy(nvl); + + return (1); +} + +int +cap_setgrent(cap_channel_t *chan) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "setgrent"); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (0); + if (nvlist_get_number(nvl, "error") != 0) { + errno = nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (0); + } + nvlist_destroy(nvl); + + return (1); +} + +void +cap_endgrent(cap_channel_t *chan) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "endgrent"); + /* Ignore any errors, we have no way to report them. */ + nvlist_destroy(cap_xfer_nvlist(chan, nvl)); +} + +int +cap_grp_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds) +{ + nvlist_t *limits, *nvl; + unsigned int i; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) { + limits = nvlist_create(0); + } else { + if (nvlist_exists_nvlist(limits, "cmds")) + nvlist_free_nvlist(limits, "cmds"); + } + nvl = nvlist_create(0); + for (i = 0; i < ncmds; i++) + nvlist_add_null(nvl, cmds[i]); + nvlist_move_nvlist(limits, "cmds", nvl); + return (cap_limit_set(chan, limits)); +} + +int +cap_grp_limit_fields(cap_channel_t *chan, const char * const *fields, + size_t nfields) +{ + nvlist_t *limits, *nvl; + unsigned int i; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) { + limits = nvlist_create(0); + } else { + if (nvlist_exists_nvlist(limits, "fields")) + nvlist_free_nvlist(limits, "fields"); + } + nvl = nvlist_create(0); + for (i = 0; i < nfields; i++) + nvlist_add_null(nvl, fields[i]); + nvlist_move_nvlist(limits, "fields", nvl); + return (cap_limit_set(chan, limits)); +} + +int +cap_grp_limit_groups(cap_channel_t *chan, const char * const *names, + size_t nnames, const gid_t *gids, size_t ngids) +{ + nvlist_t *limits, *groups; + unsigned int i; + char nvlname[64]; + int n; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) { + limits = nvlist_create(0); + } else { + if (nvlist_exists_nvlist(limits, "groups")) + nvlist_free_nvlist(limits, "groups"); + } + groups = nvlist_create(0); + for (i = 0; i < ngids; i++) { + n = snprintf(nvlname, sizeof(nvlname), "gid%u", i); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_number(groups, nvlname, (uint64_t)gids[i]); + } + for (i = 0; i < nnames; i++) { + n = snprintf(nvlname, sizeof(nvlname), "gid%u", i); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_string(groups, nvlname, names[i]); + } + nvlist_move_nvlist(limits, "groups", groups); + return (cap_limit_set(chan, limits)); +} + +/* + * Service functions. + */ +static bool +grp_allowed_cmd(const nvlist_t *limits, const char *cmd) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed commands, then all commands + * are allowed. + */ + if (!nvlist_exists_nvlist(limits, "cmds")) + return (true); + + limits = nvlist_get_nvlist(limits, "cmds"); + return (nvlist_exists_null(limits, cmd)); +} + +static int +grp_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!grp_allowed_cmd(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +grp_allowed_group(const nvlist_t *limits, const char *gname, gid_t gid) +{ + const char *name; + void *cookie; + int type; + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed groups, then all groups are allowed. + */ + if (!nvlist_exists_nvlist(limits, "groups")) + return (true); + + limits = nvlist_get_nvlist(limits, "groups"); + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + if (gid != (gid_t)-1 && + nvlist_get_number(limits, name) == (uint64_t)gid) { + return (true); + } + break; + case NV_TYPE_STRING: + if (gname != NULL && + strcmp(nvlist_get_string(limits, name), + gname) == 0) { + return (true); + } + break; + default: + abort(); + } + } + + return (false); +} + +static int +grp_allowed_groups(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name, *gname; + void *cookie; + gid_t gid; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + gid = (gid_t)nvlist_get_number(newlimits, name); + gname = NULL; + break; + case NV_TYPE_STRING: + gid = (gid_t)-1; + gname = nvlist_get_string(newlimits, name); + break; + default: + return (EINVAL); + } + if (!grp_allowed_group(oldlimits, gname, gid)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +grp_allowed_field(const nvlist_t *limits, const char *field) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed fields, then all fields are allowed. + */ + if (!nvlist_exists_nvlist(limits, "fields")) + return (true); + + limits = nvlist_get_nvlist(limits, "fields"); + return (nvlist_exists_null(limits, field)); +} + +static int +grp_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!grp_allowed_field(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +grp_pack(const nvlist_t *limits, const struct group *grp, nvlist_t *nvl) +{ + char nvlname[64]; + int n; + + if (grp == NULL) + return (true); + + /* + * If either name or GID is allowed, we allow it. + */ + if (!grp_allowed_group(limits, grp->gr_name, grp->gr_gid)) + return (false); + + if (grp_allowed_field(limits, "gr_name")) + nvlist_add_string(nvl, "gr_name", grp->gr_name); + else + nvlist_add_string(nvl, "gr_name", ""); + if (grp_allowed_field(limits, "gr_passwd")) + nvlist_add_string(nvl, "gr_passwd", grp->gr_passwd); + else + nvlist_add_string(nvl, "gr_passwd", ""); + if (grp_allowed_field(limits, "gr_gid")) + nvlist_add_number(nvl, "gr_gid", (uint64_t)grp->gr_gid); + else + nvlist_add_number(nvl, "gr_gid", (uint64_t)-1); + if (grp_allowed_field(limits, "gr_mem") && grp->gr_mem[0] != NULL) { + unsigned int ngroups; + + for (ngroups = 0; grp->gr_mem[ngroups] != NULL; ngroups++) { + n = snprintf(nvlname, sizeof(nvlname), "gr_mem[%u]", + ngroups); + assert(n > 0 && n < (ssize_t)sizeof(nvlname)); + nvlist_add_string(nvl, nvlname, grp->gr_mem[ngroups]); + } + nvlist_add_number(nvl, "gr_nmem", (uint64_t)ngroups); + } + + return (true); +} + +static int +grp_getgrent(const nvlist_t *limits, const nvlist_t *nvlin __unused, + nvlist_t *nvlout) +{ + struct group *grp; + + for (;;) { + errno = 0; + grp = getgrent(); + if (errno != 0) + return (errno); + if (grp_pack(limits, grp, nvlout)) + return (0); + } + + /* NOTREACHED */ +} + +static int +grp_getgrnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct group *grp; + const char *name; + + if (!nvlist_exists_string(nvlin, "name")) + return (EINVAL); + name = nvlist_get_string(nvlin, "name"); + assert(name != NULL); + + errno = 0; + grp = getgrnam(name); + if (errno != 0) + return (errno); + + (void)grp_pack(limits, grp, nvlout); + + return (0); +} + +static int +grp_getgrgid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct group *grp; + gid_t gid; + + if (!nvlist_exists_number(nvlin, "gid")) + return (EINVAL); + + gid = (gid_t)nvlist_get_number(nvlin, "gid"); + + errno = 0; + grp = getgrgid(gid); + if (errno != 0) + return (errno); + + (void)grp_pack(limits, grp, nvlout); + + return (0); +} + +static int +grp_setgroupent(const nvlist_t *limits __unused, const nvlist_t *nvlin, + nvlist_t *nvlout __unused) +{ + int stayopen; + + if (!nvlist_exists_bool(nvlin, "stayopen")) + return (EINVAL); + + stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0; + + return (setgroupent(stayopen) == 0 ? EFAULT : 0); +} + +static int +grp_setgrent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused, + nvlist_t *nvlout __unused) +{ + + setgrent(); + + return (0); +} + +static int +grp_endgrent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused, + nvlist_t *nvlout __unused) +{ + + endgrent(); + + return (0); +} + +static int +grp_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const nvlist_t *limits; + const char *name; + void *cookie; + int error, type; + + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") && + !nvlist_exists_nvlist(newlimits, "cmds")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") && + !nvlist_exists_nvlist(newlimits, "fields")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "groups") && + !nvlist_exists_nvlist(newlimits, "groups")) { + return (ENOTCAPABLE); + } + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NVLIST) + return (EINVAL); + limits = nvlist_get_nvlist(newlimits, name); + if (strcmp(name, "cmds") == 0) + error = grp_allowed_cmds(oldlimits, limits); + else if (strcmp(name, "fields") == 0) + error = grp_allowed_fields(oldlimits, limits); + else if (strcmp(name, "groups") == 0) + error = grp_allowed_groups(oldlimits, limits); + else + error = EINVAL; + if (error != 0) + return (error); + } + + return (0); +} + +static int +grp_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (!grp_allowed_cmd(limits, cmd)) + return (ENOTCAPABLE); + + if (strcmp(cmd, "getgrent") == 0 || strcmp(cmd, "getgrent_r") == 0) + error = grp_getgrent(limits, nvlin, nvlout); + else if (strcmp(cmd, "getgrnam") == 0 || strcmp(cmd, "getgrnam_r") == 0) + error = grp_getgrnam(limits, nvlin, nvlout); + else if (strcmp(cmd, "getgrgid") == 0 || strcmp(cmd, "getgrgid_r") == 0) + error = grp_getgrgid(limits, nvlin, nvlout); + else if (strcmp(cmd, "setgroupent") == 0) + error = grp_setgroupent(limits, nvlin, nvlout); + else if (strcmp(cmd, "setgrent") == 0) + error = grp_setgrent(limits, nvlin, nvlout); + else if (strcmp(cmd, "endgrent") == 0) + error = grp_endgrent(limits, nvlin, nvlout); + else + error = EINVAL; + + return (error); +} + +CREATE_SERVICE("system.grp", grp_limit, grp_command, 0); diff --git a/lib/libcasper/services/cap_grp/cap_grp.h b/lib/libcasper/services/cap_grp/cap_grp.h new file mode 100644 index 000000000000..9be2e7a78dd5 --- /dev/null +++ b/lib/libcasper/services/cap_grp/cap_grp.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_GRP_H_ +#define _CAP_GRP_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/cdefs.h> + +#ifdef WITH_CASPER +__BEGIN_DECLS + +struct group *cap_getgrent(cap_channel_t *chan); +struct group *cap_getgrnam(cap_channel_t *chan, const char *name); +struct group *cap_getgrgid(cap_channel_t *chan, gid_t gid); + +int cap_getgrent_r(cap_channel_t *chan, struct group *grp, char *buffer, + size_t bufsize, struct group **result); +int cap_getgrnam_r(cap_channel_t *chan, const char *name, struct group *grp, + char *buffer, size_t bufsize, struct group **result); +int cap_getgrgid_r(cap_channel_t *chan, gid_t gid, struct group *grp, + char *buffer, size_t bufsize, struct group **result); + +int cap_setgroupent(cap_channel_t *chan, int stayopen); +int cap_setgrent(cap_channel_t *chan); +void cap_endgrent(cap_channel_t *chan); + +int cap_grp_limit_cmds(cap_channel_t *chan, const char * const *cmds, + size_t ncmds); +int cap_grp_limit_fields(cap_channel_t *chan, const char * const *fields, + size_t nfields); +int cap_grp_limit_groups(cap_channel_t *chan, const char * const *names, + size_t nnames, const gid_t *gids, size_t ngids); + +__END_DECLS + +#else +#define cap_getgrent(chan) getgrent() +#define cap_getgrnam(chan, name) getgrnam(name) +#define cap_getgrgid(chan, gid) getgrgid(gid) + +#define cap_setgroupent(chan, stayopen) etgroupent(stayopen) +#define endgrent(chan) endgrent() +static inline int +cap_setgrent(cap_channel_t *chan __unused) +{ + + setgrent(); + return(0); +} + +#define cap_getgrent_r(chan, grp, buffer, bufsize, result) \ + getgrent_r(grp, buffer, bufsize, result) +#define cap_getgrnam_r(chan, name, grp, buffer, bufsize, result) \ + getgrnam_r(name, grp, buffer, bufsize, result) +#define cap_getgrgid_r(chan, gid, grp, buffer, bufsize, result) \ + getgrgid_r(gid, grp, buffer, bufsize, result) + +#define cap_grp_limit_cmds(chan, cmds, ncmds) (0) +#define cap_grp_limit_fields(chan, fields, nfields) (0) +#define cap_grp_limit_groups(chan, names, nnames, gids, ngids) (0) + +#endif + +#endif /* !_CAP_GRP_H_ */ diff --git a/lib/libcasper/services/cap_grp/tests/Makefile b/lib/libcasper/services/cap_grp/tests/Makefile new file mode 100644 index 000000000000..f57d58972c52 --- /dev/null +++ b/lib/libcasper/services/cap_grp/tests/Makefile @@ -0,0 +1,12 @@ +.include <src.opts.mk> + +TAP_TESTS_C= grp_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_grp +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_grp/tests/Makefile.depend b/lib/libcasper/services/cap_grp/tests/Makefile.depend new file mode 100644 index 000000000000..fd331b1176a3 --- /dev/null +++ b/lib/libcasper/services/cap_grp/tests/Makefile.depend @@ -0,0 +1,18 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcasper/services/cap_grp \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_grp/tests/grp_test.c b/lib/libcasper/services/cap_grp/tests/grp_test.c new file mode 100644 index 000000000000..d7e9cf12954a --- /dev/null +++ b/lib/libcasper/services/cap_grp/tests/grp_test.c @@ -0,0 +1,1556 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/capsicum.h> +#include <sys/nv.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <grp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> + +#include <casper/cap_grp.h> + +static int ntest = 1; + +#define CHECK(expr) do { \ + if ((expr)) \ + printf("ok %d %s:%u\n", ntest, __FILE__, __LINE__); \ + else \ + printf("not ok %d %s:%u\n", ntest, __FILE__, __LINE__); \ + fflush(stdout); \ + ntest++; \ +} while (0) +#define CHECKX(expr) do { \ + if ((expr)) { \ + printf("ok %d %s:%u\n", ntest, __FILE__, __LINE__); \ + } else { \ + printf("not ok %d %s:%u\n", ntest, __FILE__, __LINE__); \ + exit(1); \ + } \ + fflush(stdout); \ + ntest++; \ +} while (0) + +#define GID_WHEEL 0 +#define GID_OPERATOR 5 + +#define GETGRENT0 0x0001 +#define GETGRENT1 0x0002 +#define GETGRENT2 0x0004 +#define GETGRENT (GETGRENT0 | GETGRENT1 | GETGRENT2) +#define GETGRENT_R0 0x0008 +#define GETGRENT_R1 0x0010 +#define GETGRENT_R2 0x0020 +#define GETGRENT_R (GETGRENT_R0 | GETGRENT_R1 | GETGRENT_R2) +#define GETGRNAM 0x0040 +#define GETGRNAM_R 0x0080 +#define GETGRGID 0x0100 +#define GETGRGID_R 0x0200 +#define SETGRENT 0x0400 + +static bool +group_mem_compare(char **mem0, char **mem1) +{ + int i0, i1; + + if (mem0 == NULL && mem1 == NULL) + return (true); + if (mem0 == NULL || mem1 == NULL) + return (false); + + for (i0 = 0; mem0[i0] != NULL; i0++) { + for (i1 = 0; mem1[i1] != NULL; i1++) { + if (strcmp(mem0[i0], mem1[i1]) == 0) + break; + } + if (mem1[i1] == NULL) + return (false); + } + + return (true); +} + +static bool +group_compare(const struct group *grp0, const struct group *grp1) +{ + + if (grp0 == NULL && grp1 == NULL) + return (true); + if (grp0 == NULL || grp1 == NULL) + return (false); + + if (strcmp(grp0->gr_name, grp1->gr_name) != 0) + return (false); + + if (grp0->gr_passwd != NULL || grp1->gr_passwd != NULL) { + if (grp0->gr_passwd == NULL || grp1->gr_passwd == NULL) + return (false); + if (strcmp(grp0->gr_passwd, grp1->gr_passwd) != 0) + return (false); + } + + if (grp0->gr_gid != grp1->gr_gid) + return (false); + + if (!group_mem_compare(grp0->gr_mem, grp1->gr_mem)) + return (false); + + return (true); +} + +static unsigned int +runtest_cmds(cap_channel_t *capgrp) +{ + char bufs[1024], bufc[1024]; + unsigned int result; + struct group *grps, *grpc; + struct group sts, stc; + + result = 0; + + (void)setgrent(); + if (cap_setgrent(capgrp) == 1) + result |= SETGRENT; + + grps = getgrent(); + grpc = cap_getgrent(capgrp); + if (group_compare(grps, grpc)) { + result |= GETGRENT0; + grps = getgrent(); + grpc = cap_getgrent(capgrp); + if (group_compare(grps, grpc)) + result |= GETGRENT1; + } + + getgrent_r(&sts, bufs, sizeof(bufs), &grps); + cap_getgrent_r(capgrp, &stc, bufc, sizeof(bufc), &grpc); + if (group_compare(grps, grpc)) { + result |= GETGRENT_R0; + getgrent_r(&sts, bufs, sizeof(bufs), &grps); + cap_getgrent_r(capgrp, &stc, bufc, sizeof(bufc), &grpc); + if (group_compare(grps, grpc)) + result |= GETGRENT_R1; + } + + (void)setgrent(); + if (cap_setgrent(capgrp) == 1) + result |= SETGRENT; + + getgrent_r(&sts, bufs, sizeof(bufs), &grps); + cap_getgrent_r(capgrp, &stc, bufc, sizeof(bufc), &grpc); + if (group_compare(grps, grpc)) + result |= GETGRENT_R2; + + grps = getgrent(); + grpc = cap_getgrent(capgrp); + if (group_compare(grps, grpc)) + result |= GETGRENT2; + + grps = getgrnam("wheel"); + grpc = cap_getgrnam(capgrp, "wheel"); + if (group_compare(grps, grpc)) { + grps = getgrnam("operator"); + grpc = cap_getgrnam(capgrp, "operator"); + if (group_compare(grps, grpc)) + result |= GETGRNAM; + } + + getgrnam_r("wheel", &sts, bufs, sizeof(bufs), &grps); + cap_getgrnam_r(capgrp, "wheel", &stc, bufc, sizeof(bufc), &grpc); + if (group_compare(grps, grpc)) { + getgrnam_r("operator", &sts, bufs, sizeof(bufs), &grps); + cap_getgrnam_r(capgrp, "operator", &stc, bufc, sizeof(bufc), + &grpc); + if (group_compare(grps, grpc)) + result |= GETGRNAM_R; + } + + grps = getgrgid(GID_WHEEL); + grpc = cap_getgrgid(capgrp, GID_WHEEL); + if (group_compare(grps, grpc)) { + grps = getgrgid(GID_OPERATOR); + grpc = cap_getgrgid(capgrp, GID_OPERATOR); + if (group_compare(grps, grpc)) + result |= GETGRGID; + } + + getgrgid_r(GID_WHEEL, &sts, bufs, sizeof(bufs), &grps); + cap_getgrgid_r(capgrp, GID_WHEEL, &stc, bufc, sizeof(bufc), &grpc); + if (group_compare(grps, grpc)) { + getgrgid_r(GID_OPERATOR, &sts, bufs, sizeof(bufs), &grps); + cap_getgrgid_r(capgrp, GID_OPERATOR, &stc, bufc, sizeof(bufc), + &grpc); + if (group_compare(grps, grpc)) + result |= GETGRGID_R; + } + + return (result); +} + +static void +test_cmds(cap_channel_t *origcapgrp) +{ + cap_channel_t *capgrp; + const char *cmds[7], *fields[4], *names[5]; + gid_t gids[5]; + + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + + names[0] = "wheel"; + names[1] = "daemon"; + names[2] = "kmem"; + names[3] = "sys"; + names[4] = "operator"; + + gids[0] = 0; + gids[1] = 1; + gids[2] = 2; + gids[3] = 3; + gids[4] = 5; + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == 0); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == 0); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: setgrent + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cap_setgrent(capgrp); + + cmds[0] = "getgrent"; + cmds[1] = "getgrent_r"; + cmds[2] = "getgrnam"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "setgrent"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (GETGRENT0 | GETGRENT1 | GETGRENT_R0 | + GETGRENT_R1 | GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: setgrent + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cap_setgrent(capgrp); + + cmds[0] = "getgrent"; + cmds[1] = "getgrent_r"; + cmds[2] = "getgrnam"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "setgrent"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (GETGRENT0 | GETGRENT1 | GETGRENT_R0 | + GETGRENT_R1 | GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: getgrent + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent_r"; + cmds[2] = "getgrnam"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrent"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT_R2 | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getgrent + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent_r"; + cmds[2] = "getgrnam"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrent"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT_R2 | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: getgrent_r + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrnam"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrent_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT0 | GETGRENT1 | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrnam, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getgrent_r + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrnam"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrent_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT0 | GETGRENT1 | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: getgrnam + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrnam"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam_r, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getgrnam + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam_r"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrnam"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM_R | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: getgrnam_r + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrnam_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, + * getgrgid, getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getgrnam_r + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrgid"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrnam_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRGID | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: getgrgid + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrgid"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid_r + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getgrgid + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrgid"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID_R)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: wheel, daemon, kmem, sys, operator + * gids: + * Disallow: + * cmds: getgrgid_r + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID)); + + cap_close(capgrp); + + /* + * Allow: + * cmds: setgrent, getgrent, getgrent_r, getgrnam, getgrnam_r, + * getgrgid + * fields: gr_name, gr_passwd, gr_gid, gr_mem + * groups: + * names: + * gids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getgrgid_r + * fields: + * groups: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 6) == 0); + cmds[0] = "setgrent"; + cmds[1] = "getgrent"; + cmds[2] = "getgrent_r"; + cmds[3] = "getgrnam"; + cmds[4] = "getgrnam_r"; + cmds[5] = "getgrgid"; + cmds[6] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getgrgid_r"; + CHECK(cap_grp_limit_cmds(capgrp, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 5) == 0); + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID)); + + cap_close(capgrp); +} + +#define GR_NAME 0x01 +#define GR_PASSWD 0x02 +#define GR_GID 0x04 +#define GR_MEM 0x08 + +static unsigned int +group_fields(const struct group *grp) +{ + unsigned int result; + + result = 0; + + if (grp->gr_name != NULL && grp->gr_name[0] != '\0') + result |= GR_NAME; + + if (grp->gr_passwd != NULL && grp->gr_passwd[0] != '\0') + result |= GR_PASSWD; + + if (grp->gr_gid != (gid_t)-1) + result |= GR_GID; + + if (grp->gr_mem != NULL && grp->gr_mem[0] != NULL) + result |= GR_MEM; + + return (result); +} + +static bool +runtest_fields(cap_channel_t *capgrp, unsigned int expected) +{ + char buf[1024]; + struct group *grp; + struct group st; + + (void)cap_setgrent(capgrp); + grp = cap_getgrent(capgrp); + if (group_fields(grp) != expected) + return (false); + + (void)cap_setgrent(capgrp); + cap_getgrent_r(capgrp, &st, buf, sizeof(buf), &grp); + if (group_fields(grp) != expected) + return (false); + + grp = cap_getgrnam(capgrp, "wheel"); + if (group_fields(grp) != expected) + return (false); + + cap_getgrnam_r(capgrp, "wheel", &st, buf, sizeof(buf), &grp); + if (group_fields(grp) != expected) + return (false); + + grp = cap_getgrgid(capgrp, GID_WHEEL); + if (group_fields(grp) != expected) + return (false); + + cap_getgrgid_r(capgrp, GID_WHEEL, &st, buf, sizeof(buf), &grp); + if (group_fields(grp) != expected) + return (false); + + return (true); +} + +static void +test_fields(cap_channel_t *origcapgrp) +{ + cap_channel_t *capgrp; + const char *fields[4]; + + /* No limits. */ + + CHECK(runtest_fields(origcapgrp, GR_NAME | GR_PASSWD | GR_GID | GR_MEM)); + + /* + * Allow: + * fields: gr_name, gr_passwd, gr_gid, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == 0); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD | GR_GID | GR_MEM)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_passwd, gr_gid, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_passwd"; + fields[1] = "gr_gid"; + fields[2] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_PASSWD | GR_GID | GR_MEM)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_name, gr_gid, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_gid"; + fields[2] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_passwd"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_GID | GR_MEM)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_name, gr_passwd, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_gid"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD | GR_MEM)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_name, gr_passwd, gr_gid + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + CHECK(cap_grp_limit_fields(capgrp, fields, 3) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD | GR_GID)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_name, gr_passwd + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_gid"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_PASSWD)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_name, gr_gid + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_gid"; + CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_GID)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_name, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_name"; + fields[1] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_passwd"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_NAME | GR_MEM)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_passwd, gr_gid + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_passwd"; + fields[1] = "gr_gid"; + CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_PASSWD | GR_GID)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_passwd, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_passwd"; + fields[1] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_gid"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_PASSWD | GR_MEM)); + + cap_close(capgrp); + + /* + * Allow: + * fields: gr_gid, gr_mem + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + fields[0] = "gr_gid"; + fields[1] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 2) == 0); + fields[0] = "gr_name"; + fields[1] = "gr_passwd"; + fields[2] = "gr_gid"; + fields[3] = "gr_mem"; + CHECK(cap_grp_limit_fields(capgrp, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "gr_passwd"; + CHECK(cap_grp_limit_fields(capgrp, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(capgrp, GR_GID | GR_MEM)); + + cap_close(capgrp); +} + +static bool +runtest_groups(cap_channel_t *capgrp, const char **names, const gid_t *gids, + size_t ngroups) +{ + char buf[1024]; + struct group *grp; + struct group st; + unsigned int i, got; + + (void)cap_setgrent(capgrp); + got = 0; + for (;;) { + grp = cap_getgrent(capgrp); + if (grp == NULL) + break; + got++; + for (i = 0; i < ngroups; i++) { + if (strcmp(names[i], grp->gr_name) == 0 && + gids[i] == grp->gr_gid) { + break; + } + } + if (i == ngroups) + return (false); + } + if (got != ngroups) + return (false); + + (void)cap_setgrent(capgrp); + got = 0; + for (;;) { + cap_getgrent_r(capgrp, &st, buf, sizeof(buf), &grp); + if (grp == NULL) + break; + got++; + for (i = 0; i < ngroups; i++) { + if (strcmp(names[i], grp->gr_name) == 0 && + gids[i] == grp->gr_gid) { + break; + } + } + if (i == ngroups) + return (false); + } + if (got != ngroups) + return (false); + + for (i = 0; i < ngroups; i++) { + grp = cap_getgrnam(capgrp, names[i]); + if (grp == NULL) + return (false); + } + + for (i = 0; i < ngroups; i++) { + cap_getgrnam_r(capgrp, names[i], &st, buf, sizeof(buf), &grp); + if (grp == NULL) + return (false); + } + + for (i = 0; i < ngroups; i++) { + grp = cap_getgrgid(capgrp, gids[i]); + if (grp == NULL) + return (false); + } + + for (i = 0; i < ngroups; i++) { + cap_getgrgid_r(capgrp, gids[i], &st, buf, sizeof(buf), &grp); + if (grp == NULL) + return (false); + } + + return (true); +} + +static void +test_groups(cap_channel_t *origcapgrp) +{ + cap_channel_t *capgrp; + const char *names[5]; + gid_t gids[5]; + + /* + * Allow: + * groups: + * names: wheel, daemon, kmem, sys, tty + * gids: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "wheel"; + names[1] = "daemon"; + names[2] = "kmem"; + names[3] = "sys"; + names[4] = "tty"; + CHECK(cap_grp_limit_groups(capgrp, names, 5, NULL, 0) == 0); + gids[0] = 0; + gids[1] = 1; + gids[2] = 2; + gids[3] = 3; + gids[4] = 4; + + CHECK(runtest_groups(capgrp, names, gids, 5)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: kmem, sys, tty + * gids: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "kmem"; + names[1] = "sys"; + names[2] = "tty"; + CHECK(cap_grp_limit_groups(capgrp, names, 3, NULL, 0) == 0); + names[3] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 4, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "kmem"; + gids[0] = 2; + gids[1] = 3; + gids[2] = 4; + + CHECK(runtest_groups(capgrp, names, gids, 3)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: wheel, kmem, tty + * gids: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "wheel"; + names[1] = "kmem"; + names[2] = "tty"; + CHECK(cap_grp_limit_groups(capgrp, names, 3, NULL, 0) == 0); + names[3] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 4, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "wheel"; + gids[0] = 0; + gids[1] = 2; + gids[2] = 4; + + CHECK(runtest_groups(capgrp, names, gids, 3)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: + * gids: 2, 3, 4 + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "kmem"; + names[1] = "sys"; + names[2] = "tty"; + gids[0] = 2; + gids[1] = 3; + gids[2] = 4; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 3) == 0); + gids[3] = 0; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 4) == -1 && + errno == ENOTCAPABLE); + gids[0] = 0; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 && + errno == ENOTCAPABLE); + gids[0] = 2; + + CHECK(runtest_groups(capgrp, names, gids, 3)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: + * gids: 0, 2, 4 + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "wheel"; + names[1] = "kmem"; + names[2] = "tty"; + gids[0] = 0; + gids[1] = 2; + gids[2] = 4; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 3) == 0); + gids[3] = 1; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 4) == -1 && + errno == ENOTCAPABLE); + gids[0] = 1; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 && + errno == ENOTCAPABLE); + gids[0] = 0; + + CHECK(runtest_groups(capgrp, names, gids, 3)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: kmem + * gids: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "kmem"; + CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == 0); + names[1] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 2, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "kmem"; + gids[0] = 2; + + CHECK(runtest_groups(capgrp, names, gids, 1)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: wheel, tty + * gids: + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "wheel"; + names[1] = "tty"; + CHECK(cap_grp_limit_groups(capgrp, names, 2, NULL, 0) == 0); + names[2] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 3, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + CHECK(cap_grp_limit_groups(capgrp, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "wheel"; + gids[0] = 0; + gids[1] = 4; + + CHECK(runtest_groups(capgrp, names, gids, 2)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: + * gids: 2 + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "kmem"; + gids[0] = 2; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == 0); + gids[1] = 1; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 2) == -1 && + errno == ENOTCAPABLE); + gids[0] = 1; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 && + errno == ENOTCAPABLE); + gids[0] = 2; + + CHECK(runtest_groups(capgrp, names, gids, 1)); + + cap_close(capgrp); + + /* + * Allow: + * groups: + * names: + * gids: 0, 4 + */ + capgrp = cap_clone(origcapgrp); + CHECK(capgrp != NULL); + + names[0] = "wheel"; + names[1] = "tty"; + gids[0] = 0; + gids[1] = 4; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 2) == 0); + gids[2] = 1; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 3) == -1 && + errno == ENOTCAPABLE); + gids[0] = 1; + CHECK(cap_grp_limit_groups(capgrp, NULL, 0, gids, 1) == -1 && + errno == ENOTCAPABLE); + gids[0] = 0; + + CHECK(runtest_groups(capgrp, names, gids, 2)); + + cap_close(capgrp); +} + +int +main(void) +{ + cap_channel_t *capcas, *capgrp; + + printf("1..199\n"); + fflush(stdout); + + capcas = cap_init(); + CHECKX(capcas != NULL); + + capgrp = cap_service_open(capcas, "system.grp"); + CHECKX(capgrp != NULL); + + cap_close(capcas); + + /* No limits. */ + + CHECK(runtest_cmds(capgrp) == (SETGRENT | GETGRENT | GETGRENT_R | + GETGRNAM | GETGRNAM_R | GETGRGID | GETGRGID_R)); + + test_cmds(capgrp); + + test_fields(capgrp); + + test_groups(capgrp); + + cap_close(capgrp); + + exit(0); +} diff --git a/lib/libcasper/services/cap_net/Makefile b/lib/libcasper/services/cap_net/Makefile new file mode 100644 index 000000000000..1ba35a674a05 --- /dev/null +++ b/lib/libcasper/services/cap_net/Makefile @@ -0,0 +1,46 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE=libcasper + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_net + +SRCS= cap_net.c +.endif + +INCS= cap_net.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-DWITH_CASPER + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_net.3 + +MLINKS+=cap_net.3 libcap_net.3 +MLINKS+=cap_net.3 cap_bind.3 +MLINKS+=cap_net.3 cap_connect.3 +MLINKS+=cap_net.3 cap_net_free.3 +MLINKS+=cap_net.3 cap_net_limit.3 +MLINKS+=cap_net.3 cap_net_limit_addr2name.3 +MLINKS+=cap_net.3 cap_net_limit_addr2name_family.3 +MLINKS+=cap_net.3 cap_net_limit_bind.3 +MLINKS+=cap_net.3 cap_net_limit_connect.3 +MLINKS+=cap_net.3 cap_net_limit_init.3 +MLINKS+=cap_net.3 cap_net_limit_name2addr.3 +MLINKS+=cap_net.3 cap_net_limit_name2addr_family.3 +MLINKS+=cap_net.3 cap_getaddrinfo.3 +MLINKS+=cap_net.3 cap_gethostbyaddr.3 +MLINKS+=cap_net.3 cap_gethostbyname.3 +MLINKS+=cap_net.3 cap_gethostbyname2.3 +MLINKS+=cap_net.3 cap_getnameinfo.3 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_net/Makefile.depend b/lib/libcasper/services/cap_net/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_net/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_net/cap_net.3 b/lib/libcasper/services/cap_net/cap_net.3 new file mode 100644 index 000000000000..e322222e866f --- /dev/null +++ b/lib/libcasper/services/cap_net/cap_net.3 @@ -0,0 +1,292 @@ +.\" Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt CAP_NET 3 +.Os +.Sh NAME +.Nm cap_bind , +.Nm cap_connect , +.Nm cap_getaddrinfo , +.Nm cap_gethostbyaddr , +.Nm cap_gethostbyname , +.Nm cap_gethostbyname2 , +.Nm cap_getnameinfo , +.Nm cap_net_free , +.Nm cap_net_limit , +.Nm cap_net_limit_addr2name , +.Nm cap_net_limit_addr2name_family , +.Nm cap_net_limit_bind , +.Nm cap_net_limit_connect , +.Nm cap_net_limit_init , +.Nm cap_net_limit_name2addr , +.Nm cap_net_limit_name2addr_family , +.Nd "library for networking in capability mode" +.Sh LIBRARY +.Lb libcap_net +.Sh SYNOPSIS +.In sys/nv.h +.In libcasper.h +.In casper/cap_net.h +.Ft int +.Fn cap_bind "cap_channel_t *chan" "int s" "const struct sockaddr *addr" "socklen_t addrlen" +.Ft int +.Fn cap_connect "cap_channel_t *chan" "int s" "const struct sockaddr *name" "socklen_t namelen" +.Ft int +.Fn cap_getaddrinfo "cap_channel_t *chan" "const char *hostname" "const char *servname" "const struct addrinfo *hints" "struct addrinfo **res" +.Ft int +.Fn cap_getnameinfo "cap_channel_t *chan" "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" +.Ft "struct hostent *" +.Fn cap_gethostbyname "const cap_channel_t *chan" "const char *name" +.Ft "struct hostent *" +.Fn cap_gethostbyname2 "const cap_channel_t *chan" "const char *name" "int af" +.Ft "struct hostent *" +.Fn cap_gethostbyaddr "const cap_channel_t *chan" "const void *addr" "socklen_t len" "int af" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_init "cap_channel_t *chan" "uint64_t mode" +.Ft int +.Fn cap_net_limit "cap_net_limit_t *limit" +.Ft void +.Fn cap_net_free "cap_net_limit_t *limit" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_addr2name_family "cap_net_limit_t *limit" "int *family" "size_t size" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_addr2name "cap_net_limit_t *limit" "const struct sockaddr *sa" "socklen_t salen" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_name2addr_family "cap_net_limit_t *limit" "int *family" "size_t size" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_name2addr "cap_net_limit_t *limit" "const char *name" "const char *serv" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_connect "cap_net_limit_t *limit" "const struct sockaddr *sa" "socklen_t salen" +.Ft "cap_net_limit_t *" +.Fn cap_net_limit_bind "cap_net_limit_t *limit" "const struct sockaddr *sa" "socklen_t salen" +.Sh DESCRIPTION +The functions +.Fn cap_bind , +.Fn cap_connect , +.Fn cap_getaddrinfo , +.Fn cap_getnameinfo , +.Fn cap_gethostbyname , +.Fn cap_gethostbyname2 , +and +.Fn cap_gethostbyaddr +provide a set of APIs equivalent to +.Xr bind 2 , +.Xr connect 2 , +.Xr getaddrinfo 3 , +.Xr getnameinfo 3 , +.Xr gethostbyname 3 , +.Xr gethostbyname2 3 , +and +.Xr gethostbyaddr 3 +except that a connection to the +.Nm system.net +service needs to be provided. +.Pp +These functions, as well as +.Fn cap_net_limit , +are reentrant but not thread-safe. +That is, they may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh LIMITS +By default, the cap_net capability provides unrestricted access to the network +namespace. +Applications typically only require access to a small portion of the network +namespace: +The +.Fn cap_net_limit +function can be used to restrict access to the network. +The +.Fn cap_net_limit_init +returns an opaque limit handle used to store a list of capabilities. +The +.Fa mode +restricts the functionality of the service. +Modes are encoded using the following flags: +.Pp +.Bd -literal -offset indent -compact +CAPNET_ADDR2NAME reverse DNS lookups are allowed with + cap_getnameinfo +CAPNET_NAME2ADDR name resolution is allowed with + cap_getaddrinfo +CAPNET_DEPRECATED_ADDR2NAME reverse DNS lookups are allowed with + cap_gethostbyaddr +CAPNET_DEPRECATED_NAME2ADDR name resolution is allowed with + cap_gethostbyname and cap_gethostbyname2 +CAPNET_BIND bind syscall is allowed +CAPNET_CONNECT connect syscall is allowed +CAPNET_CONNECTDNS connect syscall is allowed to the values + returned from previous call to + the cap_getaddrinfo or cap_gethostbyname +.Ed +.Pp +.Fn cap_net_limit_addr2name_family +limits the +.Fn cap_getnameinfo +and +.Fn cap_gethostbyaddr +to do reverse DNS lookups to specific family (AF_INET, AF_INET6, etc.) +.Pp +.Fn cap_net_limit_addr2name +limits the +.Fn cap_getnameinfo +and +.Fn cap_gethostbyaddr +to do reverse DNS lookups only on those specific structures. +.Pp +.Fn cap_net_limit_name2addr_family +limits the +.Fn cap_getaddrinfo , +.Fn cap_gethostbyname +and +.Fn cap_gethostbyname2 +to do the name resolution on specific family (AF_INET, AF_INET6, etc.) +.Pp +.Fn cap_net_limit_addr2name +restricts +.Fn cap_getaddrinfo , +.Fn cap_gethostbyname +and +.Fn cap_gethostbyname2 +to a set of domains. +.Pp +.Fn cap_net_limit_bind +limits +.Fn cap_bind +to bind only on those specific structures. +.Pp +.Fn cap_net_limit_connect +limits +.Fn cap_connect +to connect only on those specific structures. +If the CAPNET_CONNECTDNS is set the limits are extended to the values returned +by +.Fn cap_getaddrinfo , +.Fn cap_gethostbyname +and +.Fn cap_gethostbyname2 +In case of the +.Fn cap_getaddrinfo +the restriction is strict. +In case of the +.Fn cap_gethostbyname +and +.Fn cap_gethostbyname2 +any port will be accepted in the +.Fn cap_connect +function. +.Pp +The +.Fn cap_net_limit +will consume and apply the limits. +.Pp +Once a set of limits is applied, subsequent calls to +.Fn cap_net_limit +will fail unless the new set is a subset of the current set. +.Pp +If the +.Fn cap_net_limit +was not called the rights may be freed using +.Fn cap_net_free . +Multiple calls to +.Fn cap_net_limit_addr2name_family , +.Fn cap_net_limit_addr2name , +.Fn cap_net_limit_name2addr_family , +.Fn cap_net_limit_name2addr , +.Fn cap_net_limit_connect , +and +.Fn cap_net_limit_bind +is supported, each call is extending preview capabilities. +.Sh EXAMPLES +The following example first opens a capability to casper and then uses this +capability to create the +.Nm system.net +casper service and uses it to resolve a host and connect to it. +.Bd -literal +cap_channel_t *capcas, *capnet; +cap_net_limit_t *limit; +int familylimit, error, s; +const char *host = "example.com"; +struct addrinfo hints, *res; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Cache NLA for gai_strerror. */ +caph_cache_catpages(); + +/* Enter capability mode sandbox. */ +if (caph_enter_casper() < 0) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.net service. */ +capnet = cap_service_open(capcas, "system.net"); +if (capnet == NULL) + err(1, "Unable to open system.net service"); + +/* Close Casper capability. */ +cap_close(capcas); + +/* Limit system.net to reserve IPv4 addresses, to host example.com . */ +limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR | CAPNET_CONNECTDNS); +if (limit == NULL) + err(1, "Unable to create limits."); +cap_net_limit_name2addr(limit, host, "80"); +familylimit = AF_INET; +cap_net_limit_name2addr_family(limit, &familylimit, 1); +if (cap_net_limit(limit) < 0) + err(1, "Unable to apply limits."); + +/* Find IP addresses for the given host. */ +memset(&hints, 0, sizeof(hints)); +hints.ai_family = AF_INET; +hints.ai_socktype = SOCK_STREAM; + +error = cap_getaddrinfo(capnet, host, "80", &hints, &res); +if (error != 0) + errx(1, "cap_getaddrinfo(): %s: %s", host, gai_strerror(error)); + +s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); +if (s < 0) + err(1, "Unable to create socket"); + +if (cap_connect(capnet, s, res->ai_addr, res->ai_addrlen) < 0) + err(1, "Unable to connect to host"); +.Ed +.Sh SEE ALSO +.Xr bind 2 , +.Xr cap_enter 2 , +.Xr connect 2 , +.Xr caph_enter 3 , +.Xr err 3 , +.Xr gethostbyaddr 3 , +.Xr gethostbyname 3 , +.Xr gethostbyname2 3 , +.Xr getnameinfo 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh AUTHORS +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org diff --git a/lib/libcasper/services/cap_net/cap_net.c b/lib/libcasper/services/cap_net/cap_net.c new file mode 100644 index 000000000000..5887fe3c407e --- /dev/null +++ b/lib/libcasper/services/cap_net/cap_net.c @@ -0,0 +1,1438 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/cnv.h> +#include <sys/dnv.h> +#include <sys/nv.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_net.h" + +#define CAPNET_MASK (CAPNET_ADDR2NAME | CAPNET_NAME2ADDR \ + CAPNET_DEPRECATED_ADDR2NAME | CAPNET_DEPRECATED_NAME2ADDR | \ + CAPNET_CONNECT | CAPNET_BIND | CAPNET_CONNECTDNS) + +/* + * Defines for the names of the limits. + * XXX: we should convert all string constats to this to avoid typos. + */ +#define LIMIT_NV_BIND "bind" +#define LIMIT_NV_CONNECT "connect" +#define LIMIT_NV_ADDR2NAME "addr2name" +#define LIMIT_NV_NAME2ADDR "name2addr" + +struct cap_net_limit { + cap_channel_t *cnl_chan; + uint64_t cnl_mode; + nvlist_t *cnl_addr2name; + nvlist_t *cnl_name2addr; + nvlist_t *cnl_connect; + nvlist_t *cnl_bind; +}; + +static struct hostent hent; + +static void +hostent_free(struct hostent *hp) +{ + unsigned int ii; + + free(hp->h_name); + hp->h_name = NULL; + if (hp->h_aliases != NULL) { + for (ii = 0; hp->h_aliases[ii] != NULL; ii++) + free(hp->h_aliases[ii]); + free(hp->h_aliases); + hp->h_aliases = NULL; + } + if (hp->h_addr_list != NULL) { + for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) + free(hp->h_addr_list[ii]); + free(hp->h_addr_list); + hp->h_addr_list = NULL; + } +} + +static struct hostent * +hostent_unpack(const nvlist_t *nvl, struct hostent *hp) +{ + unsigned int ii, nitems; + char nvlname[64]; + int n; + + hostent_free(hp); + + hp->h_name = strdup(nvlist_get_string(nvl, "name")); + if (hp->h_name == NULL) + goto fail; + hp->h_addrtype = (int)nvlist_get_number(nvl, "addrtype"); + hp->h_length = (int)nvlist_get_number(nvl, "length"); + + nitems = (unsigned int)nvlist_get_number(nvl, "naliases"); + hp->h_aliases = calloc(nitems + 1, sizeof(hp->h_aliases[0])); + if (hp->h_aliases == NULL) + goto fail; + for (ii = 0; ii < nitems; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + hp->h_aliases[ii] = + strdup(nvlist_get_string(nvl, nvlname)); + if (hp->h_aliases[ii] == NULL) + goto fail; + } + hp->h_aliases[ii] = NULL; + + nitems = (unsigned int)nvlist_get_number(nvl, "naddrs"); + hp->h_addr_list = calloc(nitems + 1, sizeof(hp->h_addr_list[0])); + if (hp->h_addr_list == NULL) + goto fail; + for (ii = 0; ii < nitems; ii++) { + hp->h_addr_list[ii] = malloc(hp->h_length); + if (hp->h_addr_list[ii] == NULL) + goto fail; + n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + bcopy(nvlist_get_binary(nvl, nvlname, NULL), + hp->h_addr_list[ii], hp->h_length); + } + hp->h_addr_list[ii] = NULL; + + return (hp); +fail: + hostent_free(hp); + h_errno = NO_RECOVERY; + return (NULL); +} + +static int +request_cb(cap_channel_t *chan, const char *name, int s, + const struct sockaddr *saddr, socklen_t len) +{ + nvlist_t *nvl; + int serrno; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", name); + nvlist_add_descriptor(nvl, "s", s); + nvlist_add_binary(nvl, "saddr", saddr, len); + + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (-1); + + if (nvlist_get_number(nvl, "error") != 0) { + serrno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + errno = serrno; + return (-1); + } + + s = dup2(s, nvlist_get_descriptor(nvl, "s")); + nvlist_destroy(nvl); + + return (s == -1 ? -1 : 0); +} + +int +cap_bind(cap_channel_t *chan, int s, const struct sockaddr *addr, + socklen_t addrlen) +{ + + return (request_cb(chan, LIMIT_NV_BIND, s, addr, addrlen)); +} + +int +cap_connect(cap_channel_t *chan, int s, const struct sockaddr *name, + socklen_t namelen) +{ + + return (request_cb(chan, LIMIT_NV_CONNECT, s, name, namelen)); +} + + +struct hostent * +cap_gethostbyname(cap_channel_t *chan, const char *name) +{ + + return (cap_gethostbyname2(chan, name, AF_INET)); +} + +struct hostent * +cap_gethostbyname2(cap_channel_t *chan, const char *name, int af) +{ + struct hostent *hp; + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "gethostbyname"); + nvlist_add_number(nvl, "family", (uint64_t)af); + nvlist_add_string(nvl, "name", name); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + h_errno = NO_RECOVERY; + return (NULL); + } + if (nvlist_get_number(nvl, "error") != 0) { + h_errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (NULL); + } + + hp = hostent_unpack(nvl, &hent); + nvlist_destroy(nvl); + return (hp); +} + +struct hostent * +cap_gethostbyaddr(cap_channel_t *chan, const void *addr, socklen_t len, + int af) +{ + struct hostent *hp; + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "gethostbyaddr"); + nvlist_add_binary(nvl, "addr", addr, (size_t)len); + nvlist_add_number(nvl, "family", (uint64_t)af); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + h_errno = NO_RECOVERY; + return (NULL); + } + if (nvlist_get_number(nvl, "error") != 0) { + h_errno = (int)nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (NULL); + } + hp = hostent_unpack(nvl, &hent); + nvlist_destroy(nvl); + return (hp); +} + +static struct addrinfo * +addrinfo_unpack(const nvlist_t *nvl) +{ + struct addrinfo *ai; + const void *addr; + size_t addrlen; + const char *canonname; + + addr = nvlist_get_binary(nvl, "ai_addr", &addrlen); + ai = malloc(sizeof(*ai) + addrlen); + if (ai == NULL) + return (NULL); + ai->ai_flags = (int)nvlist_get_number(nvl, "ai_flags"); + ai->ai_family = (int)nvlist_get_number(nvl, "ai_family"); + ai->ai_socktype = (int)nvlist_get_number(nvl, "ai_socktype"); + ai->ai_protocol = (int)nvlist_get_number(nvl, "ai_protocol"); + ai->ai_addrlen = (socklen_t)addrlen; + canonname = dnvlist_get_string(nvl, "ai_canonname", NULL); + if (canonname != NULL) { + ai->ai_canonname = strdup(canonname); + if (ai->ai_canonname == NULL) { + free(ai); + return (NULL); + } + } else { + ai->ai_canonname = NULL; + } + ai->ai_addr = (void *)(ai + 1); + bcopy(addr, ai->ai_addr, addrlen); + ai->ai_next = NULL; + + return (ai); +} + +int +cap_getaddrinfo(cap_channel_t *chan, const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct addrinfo *firstai, *prevai, *curai; + unsigned int ii; + const nvlist_t *nvlai; + char nvlname[64]; + nvlist_t *nvl; + int error, serrno, n; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "getaddrinfo"); + if (hostname != NULL) + nvlist_add_string(nvl, "hostname", hostname); + if (servname != NULL) + nvlist_add_string(nvl, "servname", servname); + if (hints != NULL) { + nvlist_add_number(nvl, "hints.ai_flags", + (uint64_t)hints->ai_flags); + nvlist_add_number(nvl, "hints.ai_family", + (uint64_t)hints->ai_family); + nvlist_add_number(nvl, "hints.ai_socktype", + (uint64_t)hints->ai_socktype); + nvlist_add_number(nvl, "hints.ai_protocol", + (uint64_t)hints->ai_protocol); + } + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (EAI_MEMORY); + if (nvlist_get_number(nvl, "error") != 0) { + error = (int)nvlist_get_number(nvl, "error"); + serrno = dnvlist_get_number(nvl, "errno", 0); + nvlist_destroy(nvl); + errno = (error == EAI_SYSTEM) ? serrno : 0; + return (error); + } + + nvlai = NULL; + firstai = prevai = curai = NULL; + for (ii = 0; ; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "res%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + if (!nvlist_exists_nvlist(nvl, nvlname)) + break; + nvlai = nvlist_get_nvlist(nvl, nvlname); + curai = addrinfo_unpack(nvlai); + if (curai == NULL) { + nvlist_destroy(nvl); + return (EAI_MEMORY); + } + if (prevai != NULL) + prevai->ai_next = curai; + else + firstai = curai; + prevai = curai; + } + nvlist_destroy(nvl); + if (curai == NULL && nvlai != NULL) { + if (firstai == NULL) + freeaddrinfo(firstai); + return (EAI_MEMORY); + } + + *res = firstai; + return (0); +} + +int +cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, char *serv, size_t servlen, int flags) +{ + nvlist_t *nvl; + int error, serrno; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "getnameinfo"); + nvlist_add_number(nvl, "hostlen", (uint64_t)hostlen); + nvlist_add_number(nvl, "servlen", (uint64_t)servlen); + nvlist_add_binary(nvl, "sa", sa, (size_t)salen); + nvlist_add_number(nvl, "flags", (uint64_t)flags); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (EAI_MEMORY); + if (nvlist_get_number(nvl, "error") != 0) { + error = (int)nvlist_get_number(nvl, "error"); + serrno = dnvlist_get_number(nvl, "errno", 0); + nvlist_destroy(nvl); + errno = (error == EAI_SYSTEM) ? serrno : 0; + return (error); + } + + if (host != NULL && nvlist_exists_string(nvl, "host")) + strlcpy(host, nvlist_get_string(nvl, "host"), hostlen); + if (serv != NULL && nvlist_exists_string(nvl, "serv")) + strlcpy(serv, nvlist_get_string(nvl, "serv"), servlen); + nvlist_destroy(nvl); + return (0); +} + +cap_net_limit_t * +cap_net_limit_init(cap_channel_t *chan, uint64_t mode) +{ + cap_net_limit_t *limit; + + limit = calloc(1, sizeof(*limit)); + if (limit != NULL) { + limit->cnl_mode = mode; + limit->cnl_chan = chan; + limit->cnl_addr2name = nvlist_create(0); + limit->cnl_name2addr = nvlist_create(0); + limit->cnl_connect = nvlist_create(0); + limit->cnl_bind = nvlist_create(0); + } + + return (limit); +} + +static void +pack_limit(nvlist_t *lnvl, const char *name, nvlist_t *limit) +{ + + if (!nvlist_empty(limit)) { + nvlist_move_nvlist(lnvl, name, limit); + } else { + nvlist_destroy(limit); + } +} + +int +cap_net_limit(cap_net_limit_t *limit) +{ + nvlist_t *lnvl; + cap_channel_t *chan; + + lnvl = nvlist_create(0); + nvlist_add_number(lnvl, "mode", limit->cnl_mode); + + pack_limit(lnvl, LIMIT_NV_ADDR2NAME, limit->cnl_addr2name); + pack_limit(lnvl, LIMIT_NV_NAME2ADDR, limit->cnl_name2addr); + pack_limit(lnvl, LIMIT_NV_CONNECT, limit->cnl_connect); + pack_limit(lnvl, LIMIT_NV_BIND, limit->cnl_bind); + + chan = limit->cnl_chan; + free(limit); + + return (cap_limit_set(chan, lnvl)); +} + +void +cap_net_free(cap_net_limit_t *limit) +{ + + if (limit == NULL) + return; + + nvlist_destroy(limit->cnl_addr2name); + nvlist_destroy(limit->cnl_name2addr); + nvlist_destroy(limit->cnl_connect); + nvlist_destroy(limit->cnl_bind); + + free(limit); +} + +static void +pack_family(nvlist_t *nvl, int *family, size_t size) +{ + size_t i; + + i = 0; + if (!nvlist_exists_number_array(nvl, "family")) { + uint64_t val; + + val = family[0]; + nvlist_add_number_array(nvl, "family", &val, 1); + i += 1; + } + + for (; i < size; i++) { + nvlist_append_number_array(nvl, "family", family[i]); + } +} + +static void +pack_sockaddr(nvlist_t *res, const struct sockaddr *sa, socklen_t salen) +{ + nvlist_t *nvl; + + if (!nvlist_exists_nvlist(res, "sockaddr")) { + nvl = nvlist_create(NV_FLAG_NO_UNIQUE); + } else { + nvl = nvlist_take_nvlist(res, "sockaddr"); + } + + nvlist_add_binary(nvl, "", sa, salen); + nvlist_move_nvlist(res, "sockaddr", nvl); +} + +cap_net_limit_t * +cap_net_limit_addr2name_family(cap_net_limit_t *limit, int *family, size_t size) +{ + + pack_family(limit->cnl_addr2name, family, size); + return (limit); +} + +cap_net_limit_t * +cap_net_limit_name2addr_family(cap_net_limit_t *limit, int *family, size_t size) +{ + + pack_family(limit->cnl_name2addr, family, size); + return (limit); +} + +cap_net_limit_t * +cap_net_limit_name2addr(cap_net_limit_t *limit, const char *host, + const char *serv) +{ + nvlist_t *nvl; + + if (!nvlist_exists_nvlist(limit->cnl_name2addr, "hosts")) { + nvl = nvlist_create(NV_FLAG_NO_UNIQUE); + } else { + nvl = nvlist_take_nvlist(limit->cnl_name2addr, "hosts"); + } + + nvlist_add_string(nvl, + host != NULL ? host : "", + serv != NULL ? serv : ""); + + nvlist_move_nvlist(limit->cnl_name2addr, "hosts", nvl); + return (limit); +} + +cap_net_limit_t * +cap_net_limit_addr2name(cap_net_limit_t *limit, const struct sockaddr *sa, + socklen_t salen) +{ + + pack_sockaddr(limit->cnl_addr2name, sa, salen); + return (limit); +} + + +cap_net_limit_t * +cap_net_limit_connect(cap_net_limit_t *limit, const struct sockaddr *sa, + socklen_t salen) +{ + + pack_sockaddr(limit->cnl_connect, sa, salen); + return (limit); +} + +cap_net_limit_t * +cap_net_limit_bind(cap_net_limit_t *limit, const struct sockaddr *sa, + socklen_t salen) +{ + + pack_sockaddr(limit->cnl_bind, sa, salen); + return (limit); +} + +/* + * Service functions. + */ + +static nvlist_t *capdnscache; + +static void +net_add_sockaddr_to_cache(struct sockaddr *sa, socklen_t salen, bool deprecated) +{ + void *cookie; + + if (capdnscache == NULL) { + capdnscache = nvlist_create(NV_FLAG_NO_UNIQUE); + } else { + /* Lets keep it clean. Look for dups. */ + cookie = NULL; + while (nvlist_next(capdnscache, NULL, &cookie) != NULL) { + const void *data; + size_t size; + + assert(cnvlist_type(cookie) == NV_TYPE_BINARY); + + data = cnvlist_get_binary(cookie, &size); + if (salen != size) + continue; + if (memcmp(data, sa, size) == 0) + return; + } + } + + nvlist_add_binary(capdnscache, deprecated ? "d" : "", sa, salen); +} + +static void +net_add_hostent_to_cache(const char *address, size_t asize, int family) +{ + + if (family != AF_INET && family != AF_INET6) + return; + + if (family == AF_INET6) { + struct sockaddr_in6 connaddr; + + memset(&connaddr, 0, sizeof(connaddr)); + connaddr.sin6_family = AF_INET6; + memcpy((char *)&connaddr.sin6_addr, address, asize); + connaddr.sin6_port = 0; + + net_add_sockaddr_to_cache((struct sockaddr *)&connaddr, + sizeof(connaddr), true); + } else { + struct sockaddr_in connaddr; + + memset(&connaddr, 0, sizeof(connaddr)); + connaddr.sin_family = AF_INET; + memcpy((char *)&connaddr.sin_addr.s_addr, address, asize); + connaddr.sin_port = 0; + + net_add_sockaddr_to_cache((struct sockaddr *)&connaddr, + sizeof(connaddr), true); + } +} + +static bool +net_allowed_mode(const nvlist_t *limits, uint64_t mode) +{ + + if (limits == NULL) + return (true); + + return ((nvlist_get_number(limits, "mode") & mode) == mode); +} + +static bool +net_allowed_family(const nvlist_t *limits, int family) +{ + const uint64_t *allowedfamily; + size_t i, allsize; + + if (limits == NULL) + return (true); + + /* If there are no familes at all, allow any mode. */ + if (!nvlist_exists_number_array(limits, "family")) + return (true); + + allowedfamily = nvlist_get_number_array(limits, "family", &allsize); + for (i = 0; i < allsize; i++) { + /* XXX: what with AF_UNSPEC? */ + if (allowedfamily[i] == (uint64_t)family) { + return (true); + } + } + + return (false); +} + +static bool +net_allowed_bsaddr_impl(const nvlist_t *salimits, const void *saddr, + size_t saddrsize) +{ + void *cookie; + const void *limit; + size_t limitsize; + + cookie = NULL; + while (nvlist_next(salimits, NULL, &cookie) != NULL) { + limit = cnvlist_get_binary(cookie, &limitsize); + + if (limitsize != saddrsize) { + continue; + } + if (memcmp(limit, saddr, limitsize) == 0) { + return (true); + } + + /* + * In case of deprecated version (gethostbyname) we have to + * ignore port, because there is no such info in the hostent. + * Suporting only AF_INET and AF_INET6. + */ + if (strcmp(cnvlist_name(cookie), "d") != 0 || + (saddrsize != sizeof(struct sockaddr_in) && + saddrsize != sizeof(struct sockaddr_in6))) { + continue; + } + if (saddrsize == sizeof(struct sockaddr_in)) { + const struct sockaddr_in *saddrptr; + struct sockaddr_in sockaddr; + + saddrptr = (const struct sockaddr_in *)saddr; + memcpy(&sockaddr, limit, sizeof(sockaddr)); + sockaddr.sin_port = saddrptr->sin_port; + + if (memcmp(&sockaddr, saddr, saddrsize) == 0) { + return (true); + } + } else if (saddrsize == sizeof(struct sockaddr_in6)) { + const struct sockaddr_in6 *saddrptr; + struct sockaddr_in6 sockaddr; + + saddrptr = (const struct sockaddr_in6 *)saddr; + memcpy(&sockaddr, limit, sizeof(sockaddr)); + sockaddr.sin6_port = saddrptr->sin6_port; + + if (memcmp(&sockaddr, saddr, saddrsize) == 0) { + return (true); + } + } + } + + return (false); +} + +static bool +net_allowed_bsaddr(const nvlist_t *limits, const void *saddr, size_t saddrsize) +{ + + if (limits == NULL) + return (true); + + if (!nvlist_exists_nvlist(limits, "sockaddr")) + return (true); + + return (net_allowed_bsaddr_impl(nvlist_get_nvlist(limits, "sockaddr"), + saddr, saddrsize)); +} + +static bool +net_allowed_hosts(const nvlist_t *limits, const char *name, const char *srvname) +{ + void *cookie; + const nvlist_t *hlimits; + const char *testname, *testsrvname; + + if (limits == NULL) { + return (true); + } + + /* If there are no hosts at all, allow any. */ + if (!nvlist_exists_nvlist(limits, "hosts")) { + return (true); + } + + cookie = NULL; + testname = (name == NULL ? "" : name); + testsrvname = (srvname == NULL ? "" : srvname); + hlimits = nvlist_get_nvlist(limits, "hosts"); + while (nvlist_next(hlimits, NULL, &cookie) != NULL) { + if (strcmp(cnvlist_name(cookie), "") != 0 && + strcmp(cnvlist_name(cookie), testname) != 0) { + continue; + } + + if (strcmp(cnvlist_get_string(cookie), "") != 0 && + strcmp(cnvlist_get_string(cookie), testsrvname) != 0) { + continue; + } + + return (true); + } + + return (false); +} + +static void +hostent_pack(const struct hostent *hp, nvlist_t *nvl, bool addtocache) +{ + unsigned int ii; + char nvlname[64]; + int n; + + nvlist_add_string(nvl, "name", hp->h_name); + nvlist_add_number(nvl, "addrtype", (uint64_t)hp->h_addrtype); + nvlist_add_number(nvl, "length", (uint64_t)hp->h_length); + + if (hp->h_aliases == NULL) { + nvlist_add_number(nvl, "naliases", 0); + } else { + for (ii = 0; hp->h_aliases[ii] != NULL; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "alias%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_string(nvl, nvlname, hp->h_aliases[ii]); + } + nvlist_add_number(nvl, "naliases", (uint64_t)ii); + } + + if (hp->h_addr_list == NULL) { + nvlist_add_number(nvl, "naddrs", 0); + } else { + for (ii = 0; hp->h_addr_list[ii] != NULL; ii++) { + n = snprintf(nvlname, sizeof(nvlname), "addr%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_binary(nvl, nvlname, hp->h_addr_list[ii], + (size_t)hp->h_length); + if (addtocache) { + net_add_hostent_to_cache(hp->h_addr_list[ii], + hp->h_length, hp->h_addrtype); + } + } + nvlist_add_number(nvl, "naddrs", (uint64_t)ii); + } +} + +static int +net_gethostbyname(const nvlist_t *limits, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct hostent *hp; + int family; + const nvlist_t *funclimit; + const char *name; + bool dnscache; + + if (!net_allowed_mode(limits, CAPNET_DEPRECATED_NAME2ADDR)) + return (ENOTCAPABLE); + + dnscache = net_allowed_mode(limits, CAPNET_CONNECTDNS); + funclimit = NULL; + if (limits != NULL) { + funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_NAME2ADDR, + NULL); + } + + family = (int)nvlist_get_number(nvlin, "family"); + if (!net_allowed_family(funclimit, family)) + return (ENOTCAPABLE); + + name = nvlist_get_string(nvlin, "name"); + if (!net_allowed_hosts(funclimit, name, "")) + return (ENOTCAPABLE); + + hp = gethostbyname2(name, family); + if (hp == NULL) + return (h_errno); + hostent_pack(hp, nvlout, dnscache); + return (0); +} + +static int +net_gethostbyaddr(const nvlist_t *limits, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + struct hostent *hp; + const void *addr; + size_t addrsize; + int family; + const nvlist_t *funclimit; + + if (!net_allowed_mode(limits, CAPNET_DEPRECATED_ADDR2NAME)) + return (ENOTCAPABLE); + + funclimit = NULL; + if (limits != NULL) { + funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_ADDR2NAME, + NULL); + } + + family = (int)nvlist_get_number(nvlin, "family"); + if (!net_allowed_family(funclimit, family)) + return (ENOTCAPABLE); + + addr = nvlist_get_binary(nvlin, "addr", &addrsize); + if (!net_allowed_bsaddr(funclimit, addr, addrsize)) + return (ENOTCAPABLE); + + hp = gethostbyaddr(addr, (socklen_t)addrsize, family); + if (hp == NULL) + return (h_errno); + hostent_pack(hp, nvlout, false); + return (0); +} + +static int +net_getnameinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct sockaddr_storage sast; + const void *sabin; + char *host, *serv; + size_t sabinsize, hostlen, servlen; + socklen_t salen; + int error, serrno, flags; + const nvlist_t *funclimit; + + host = serv = NULL; + if (!net_allowed_mode(limits, CAPNET_ADDR2NAME)) { + serrno = ENOTCAPABLE; + error = EAI_SYSTEM; + goto out; + } + funclimit = NULL; + if (limits != NULL) { + funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_ADDR2NAME, + NULL); + } + error = 0; + memset(&sast, 0, sizeof(sast)); + + hostlen = (size_t)nvlist_get_number(nvlin, "hostlen"); + servlen = (size_t)nvlist_get_number(nvlin, "servlen"); + + if (hostlen > 0) { + host = calloc(1, hostlen); + if (host == NULL) { + error = EAI_MEMORY; + goto out; + } + } + if (servlen > 0) { + serv = calloc(1, servlen); + if (serv == NULL) { + error = EAI_MEMORY; + goto out; + } + } + + sabin = nvlist_get_binary(nvlin, "sa", &sabinsize); + if (sabinsize > sizeof(sast)) { + error = EAI_FAIL; + goto out; + } + if (!net_allowed_bsaddr(funclimit, sabin, sabinsize)) { + serrno = ENOTCAPABLE; + error = EAI_SYSTEM; + goto out; + } + + memcpy(&sast, sabin, sabinsize); + salen = (socklen_t)sabinsize; + + if ((sast.ss_family != AF_INET || + salen != sizeof(struct sockaddr_in)) && + (sast.ss_family != AF_INET6 || + salen != sizeof(struct sockaddr_in6))) { + error = EAI_FAIL; + goto out; + } + + if (!net_allowed_family(funclimit, (int)sast.ss_family)) { + serrno = ENOTCAPABLE; + error = EAI_SYSTEM; + goto out; + } + + flags = (int)nvlist_get_number(nvlin, "flags"); + + error = getnameinfo((struct sockaddr *)&sast, salen, host, hostlen, + serv, servlen, flags); + serrno = errno; + if (error != 0) + goto out; + + if (host != NULL) + nvlist_move_string(nvlout, "host", host); + if (serv != NULL) + nvlist_move_string(nvlout, "serv", serv); +out: + if (error != 0) { + free(host); + free(serv); + if (error == EAI_SYSTEM) + nvlist_add_number(nvlout, "errno", serrno); + } + return (error); +} + +static nvlist_t * +addrinfo_pack(const struct addrinfo *ai) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_number(nvl, "ai_flags", (uint64_t)ai->ai_flags); + nvlist_add_number(nvl, "ai_family", (uint64_t)ai->ai_family); + nvlist_add_number(nvl, "ai_socktype", (uint64_t)ai->ai_socktype); + nvlist_add_number(nvl, "ai_protocol", (uint64_t)ai->ai_protocol); + nvlist_add_binary(nvl, "ai_addr", ai->ai_addr, (size_t)ai->ai_addrlen); + if (ai->ai_canonname != NULL) + nvlist_add_string(nvl, "ai_canonname", ai->ai_canonname); + + return (nvl); +} + +static int +net_getaddrinfo(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct addrinfo hints, *hintsp, *res, *cur; + const char *hostname, *servname; + char nvlname[64]; + nvlist_t *elem; + unsigned int ii; + int error, serrno, family, n; + const nvlist_t *funclimit; + bool dnscache; + + if (!net_allowed_mode(limits, CAPNET_NAME2ADDR)) { + serrno = ENOTCAPABLE; + error = EAI_SYSTEM; + goto out; + } + dnscache = net_allowed_mode(limits, CAPNET_CONNECTDNS); + funclimit = NULL; + if (limits != NULL) { + funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_NAME2ADDR, + NULL); + } + + hostname = dnvlist_get_string(nvlin, "hostname", NULL); + servname = dnvlist_get_string(nvlin, "servname", NULL); + if (nvlist_exists_number(nvlin, "hints.ai_flags")) { + hints.ai_flags = (int)nvlist_get_number(nvlin, + "hints.ai_flags"); + hints.ai_family = (int)nvlist_get_number(nvlin, + "hints.ai_family"); + hints.ai_socktype = (int)nvlist_get_number(nvlin, + "hints.ai_socktype"); + hints.ai_protocol = (int)nvlist_get_number(nvlin, + "hints.ai_protocol"); + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + hintsp = &hints; + family = hints.ai_family; + } else { + hintsp = NULL; + family = AF_UNSPEC; + } + + if (!net_allowed_family(funclimit, family)) { + errno = ENOTCAPABLE; + error = EAI_SYSTEM; + goto out; + } + if (!net_allowed_hosts(funclimit, hostname, servname)) { + errno = ENOTCAPABLE; + error = EAI_SYSTEM; + goto out; + } + error = getaddrinfo(hostname, servname, hintsp, &res); + serrno = errno; + if (error != 0) { + goto out; + } + + for (cur = res, ii = 0; cur != NULL; cur = cur->ai_next, ii++) { + elem = addrinfo_pack(cur); + n = snprintf(nvlname, sizeof(nvlname), "res%u", ii); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_move_nvlist(nvlout, nvlname, elem); + if (dnscache) { + net_add_sockaddr_to_cache(cur->ai_addr, + cur->ai_addrlen, false); + } + } + + freeaddrinfo(res); + error = 0; +out: + if (error == EAI_SYSTEM) + nvlist_add_number(nvlout, "errno", serrno); + return (error); +} + +static int +net_bind(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout) +{ + int socket, serrno; + const void *saddr; + size_t len; + const nvlist_t *funclimit; + + if (!net_allowed_mode(limits, CAPNET_BIND)) + return (ENOTCAPABLE); + funclimit = NULL; + if (limits != NULL) + funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_BIND, NULL); + + saddr = nvlist_get_binary(nvlin, "saddr", &len); + + if (!net_allowed_bsaddr(funclimit, saddr, len)) + return (ENOTCAPABLE); + + socket = nvlist_take_descriptor(nvlin, "s"); + if (bind(socket, saddr, len) < 0) { + serrno = errno; + close(socket); + return (serrno); + } + + nvlist_move_descriptor(nvlout, "s", socket); + + return (0); +} + +static int +net_connect(const nvlist_t *limits, nvlist_t *nvlin, nvlist_t *nvlout) +{ + int socket, serrno; + const void *saddr; + const nvlist_t *funclimit; + size_t len; + bool conn, conndns, allowed; + + conn = net_allowed_mode(limits, CAPNET_CONNECT); + conndns = net_allowed_mode(limits, CAPNET_CONNECTDNS); + + if (!conn && !conndns) + return (ENOTCAPABLE); + + funclimit = NULL; + if (limits != NULL) + funclimit = dnvlist_get_nvlist(limits, LIMIT_NV_CONNECT, NULL); + + saddr = nvlist_get_binary(nvlin, "saddr", &len); + allowed = false; + + if (conn && net_allowed_bsaddr(funclimit, saddr, len)) { + allowed = true; + } + if (conndns && capdnscache != NULL && + net_allowed_bsaddr_impl(capdnscache, saddr, len)) { + allowed = true; + } + + if (allowed == false) { + return (ENOTCAPABLE); + } + + socket = dup(nvlist_get_descriptor(nvlin, "s")); + if (connect(socket, saddr, len) < 0) { + serrno = errno; + close(socket); + return (serrno); + } + + nvlist_move_descriptor(nvlout, "s", socket); + + return (0); +} + +static bool +verify_only_sa_newlimts(const nvlist_t *oldfunclimits, + const nvlist_t *newfunclimit) +{ + void *cookie; + + cookie = NULL; + while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) { + void *sacookie; + + if (strcmp(cnvlist_name(cookie), "sockaddr") != 0) + return (false); + + if (cnvlist_type(cookie) != NV_TYPE_NVLIST) + return (false); + + sacookie = NULL; + while (nvlist_next(cnvlist_get_nvlist(cookie), NULL, + &sacookie) != NULL) { + const void *sa; + size_t sasize; + + if (cnvlist_type(sacookie) != NV_TYPE_BINARY) + return (false); + + sa = cnvlist_get_binary(sacookie, &sasize); + if (!net_allowed_bsaddr(oldfunclimits, sa, sasize)) + return (false); + } + } + + return (true); +} + +static bool +verify_bind_newlimts(const nvlist_t *oldlimits, + const nvlist_t *newfunclimit) +{ + const nvlist_t *oldfunclimits; + + oldfunclimits = NULL; + if (oldlimits != NULL) { + oldfunclimits = dnvlist_get_nvlist(oldlimits, LIMIT_NV_BIND, + NULL); + } + + return (verify_only_sa_newlimts(oldfunclimits, newfunclimit)); +} + + +static bool +verify_connect_newlimits(const nvlist_t *oldlimits, + const nvlist_t *newfunclimit) +{ + const nvlist_t *oldfunclimits; + + oldfunclimits = NULL; + if (oldlimits != NULL) { + oldfunclimits = dnvlist_get_nvlist(oldlimits, LIMIT_NV_CONNECT, + NULL); + } + + return (verify_only_sa_newlimts(oldfunclimits, newfunclimit)); +} + +static bool +verify_addr2name_newlimits(const nvlist_t *oldlimits, + const nvlist_t *newfunclimit) +{ + void *cookie; + const nvlist_t *oldfunclimits; + + oldfunclimits = NULL; + if (oldlimits != NULL) { + oldfunclimits = dnvlist_get_nvlist(oldlimits, + LIMIT_NV_ADDR2NAME, NULL); + } + + cookie = NULL; + while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) { + if (strcmp(cnvlist_name(cookie), "sockaddr") == 0) { + void *sacookie; + + if (cnvlist_type(cookie) != NV_TYPE_NVLIST) + return (false); + + sacookie = NULL; + while (nvlist_next(cnvlist_get_nvlist(cookie), NULL, + &sacookie) != NULL) { + const void *sa; + size_t sasize; + + if (cnvlist_type(sacookie) != NV_TYPE_BINARY) + return (false); + + sa = cnvlist_get_binary(sacookie, &sasize); + if (!net_allowed_bsaddr(oldfunclimits, sa, + sasize)) { + return (false); + } + } + } else if (strcmp(cnvlist_name(cookie), "family") == 0) { + size_t i, sfamilies; + const uint64_t *families; + + if (cnvlist_type(cookie) != NV_TYPE_NUMBER_ARRAY) + return (false); + + families = cnvlist_get_number_array(cookie, &sfamilies); + for (i = 0; i < sfamilies; i++) { + if (!net_allowed_family(oldfunclimits, + families[i])) { + return (false); + } + } + } else { + return (false); + } + } + + return (true); +} + +static bool +verify_name2addr_newlimits(const nvlist_t *oldlimits, + const nvlist_t *newfunclimit) +{ + void *cookie; + const nvlist_t *oldfunclimits; + + oldfunclimits = NULL; + if (oldlimits != NULL) { + oldfunclimits = dnvlist_get_nvlist(oldlimits, + LIMIT_NV_NAME2ADDR, NULL); + } + + cookie = NULL; + while (nvlist_next(newfunclimit, NULL, &cookie) != NULL) { + if (strcmp(cnvlist_name(cookie), "hosts") == 0) { + void *hostcookie; + + if (cnvlist_type(cookie) != NV_TYPE_NVLIST) + return (false); + + hostcookie = NULL; + while (nvlist_next(cnvlist_get_nvlist(cookie), NULL, + &hostcookie) != NULL) { + if (cnvlist_type(hostcookie) != NV_TYPE_STRING) + return (false); + + if (!net_allowed_hosts(oldfunclimits, + cnvlist_name(hostcookie), + cnvlist_get_string(hostcookie))) { + return (false); + } + } + } else if (strcmp(cnvlist_name(cookie), "family") == 0) { + size_t i, sfamilies; + const uint64_t *families; + + if (cnvlist_type(cookie) != NV_TYPE_NUMBER_ARRAY) + return (false); + + families = cnvlist_get_number_array(cookie, &sfamilies); + for (i = 0; i < sfamilies; i++) { + if (!net_allowed_family(oldfunclimits, + families[i])) { + return (false); + } + } + } else { + return (false); + } + } + + return (true); +} + +static int +net_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + bool hasmode, hasconnect, hasbind, hasaddr2name, hasname2addr; + + /* + * Modes: + * ADDR2NAME: + * getnameinfo + * DEPRECATED_ADDR2NAME: + * gethostbyaddr + * + * NAME2ADDR: + * getaddrinfo + * DEPRECATED_NAME2ADDR: + * gethostbyname + * + * Limit scheme: + * mode : NV_TYPE_NUMBER + * connect : NV_TYPE_NVLIST + * sockaddr : NV_TYPE_NVLIST + * "" : NV_TYPE_BINARY + * ... : NV_TYPE_BINARY + * bind : NV_TYPE_NVLIST + * sockaddr : NV_TYPE_NVLIST + * "" : NV_TYPE_BINARY + * ... : NV_TYPE_BINARY + * addr2name : NV_TYPE_NVLIST + * family : NV_TYPE_NUMBER_ARRAY + * sockaddr : NV_TYPE_NVLIST + * "" : NV_TYPE_BINARY + * ... : NV_TYPE_BINARY + * name2addr : NV_TYPE_NVLIST + * family : NV_TYPE_NUMBER + * hosts : NV_TYPE_NVLIST + * host : servname : NV_TYPE_STRING + */ + + hasmode = false; + hasconnect = false; + hasbind = false; + hasaddr2name = false; + hasname2addr = false; + + cookie = NULL; + while ((name = nvlist_next(newlimits, NULL, &cookie)) != NULL) { + if (strcmp(name, "mode") == 0) { + if (cnvlist_type(cookie) != NV_TYPE_NUMBER) { + return (NO_RECOVERY); + } + if (!net_allowed_mode(oldlimits, + cnvlist_get_number(cookie))) { + return (ENOTCAPABLE); + } + hasmode = true; + continue; + } + + if (cnvlist_type(cookie) != NV_TYPE_NVLIST) { + return (NO_RECOVERY); + } + + if (strcmp(name, LIMIT_NV_BIND) == 0) { + hasbind = true; + if (!verify_bind_newlimts(oldlimits, + cnvlist_get_nvlist(cookie))) { + return (ENOTCAPABLE); + } + } else if (strcmp(name, LIMIT_NV_CONNECT) == 0) { + hasconnect = true; + if (!verify_connect_newlimits(oldlimits, + cnvlist_get_nvlist(cookie))) { + return (ENOTCAPABLE); + } + } else if (strcmp(name, LIMIT_NV_ADDR2NAME) == 0) { + hasaddr2name = true; + if (!verify_addr2name_newlimits(oldlimits, + cnvlist_get_nvlist(cookie))) { + return (ENOTCAPABLE); + } + } else if (strcmp(name, LIMIT_NV_NAME2ADDR) == 0) { + hasname2addr = true; + if (!verify_name2addr_newlimits(oldlimits, + cnvlist_get_nvlist(cookie))) { + return (ENOTCAPABLE); + } + } + } + + /* Mode is required. */ + if (!hasmode) + return (ENOTCAPABLE); + + /* + * If the new limit doesn't mention mode or family we have to + * check if the current limit does have those. Missing mode or + * family in the limit means that all modes or families are + * allowed. + */ + if (oldlimits == NULL) + return (0); + if (!hasbind && nvlist_exists(oldlimits, LIMIT_NV_BIND)) + return (ENOTCAPABLE); + if (!hasconnect && nvlist_exists(oldlimits, LIMIT_NV_CONNECT)) + return (ENOTCAPABLE); + if (!hasaddr2name && nvlist_exists(oldlimits, LIMIT_NV_ADDR2NAME)) + return (ENOTCAPABLE); + if (!hasname2addr && nvlist_exists(oldlimits, LIMIT_NV_NAME2ADDR)) + return (ENOTCAPABLE); + return (0); +} + +static int +net_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + + if (strcmp(cmd, "bind") == 0) + return (net_bind(limits, nvlin, nvlout)); + else if (strcmp(cmd, "connect") == 0) + return (net_connect(limits, nvlin, nvlout)); + else if (strcmp(cmd, "gethostbyname") == 0) + return (net_gethostbyname(limits, nvlin, nvlout)); + else if (strcmp(cmd, "gethostbyaddr") == 0) + return (net_gethostbyaddr(limits, nvlin, nvlout)); + else if (strcmp(cmd, "getnameinfo") == 0) + return (net_getnameinfo(limits, nvlin, nvlout)); + else if (strcmp(cmd, "getaddrinfo") == 0) + return (net_getaddrinfo(limits, nvlin, nvlout)); + + return (EINVAL); +} + +CREATE_SERVICE("system.net", net_limit, net_command, 0); diff --git a/lib/libcasper/services/cap_net/cap_net.h b/lib/libcasper/services/cap_net/cap_net.h new file mode 100644 index 000000000000..75f936954199 --- /dev/null +++ b/lib/libcasper/services/cap_net/cap_net.h @@ -0,0 +1,159 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_NETWORK_H_ +#define _CAP_NETWORK_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/dnv.h> +#include <sys/nv.h> + +#include <sys/socket.h> + +struct addrinfo; +struct hostent; + +struct cap_net_limit; +typedef struct cap_net_limit cap_net_limit_t; + +#define CAPNET_ADDR2NAME (0x01) +#define CAPNET_NAME2ADDR (0x02) +#define CAPNET_DEPRECATED_ADDR2NAME (0x04) +#define CAPNET_DEPRECATED_NAME2ADDR (0x08) +#define CAPNET_CONNECT (0x10) +#define CAPNET_BIND (0x20) +#define CAPNET_CONNECTDNS (0x40) + +#ifdef WITH_CASPER +/* Capability functions. */ +int cap_bind(cap_channel_t *chan, int s, const struct sockaddr *addr, + socklen_t addrlen); +int cap_connect(cap_channel_t *chan, int s, const struct sockaddr *name, + socklen_t namelen); + +int cap_getaddrinfo(cap_channel_t *chan, const char *hostname, + const char *servname, const struct addrinfo *hints, struct addrinfo **res); +int cap_getnameinfo(cap_channel_t *chan, const struct sockaddr *sa, + socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, + int flags); + +/* Limit functions. */ +cap_net_limit_t *cap_net_limit_init(cap_channel_t *chan, uint64_t mode); +int cap_net_limit(cap_net_limit_t *limit); +void cap_net_free(cap_net_limit_t *limit); + +cap_net_limit_t *cap_net_limit_addr2name_family(cap_net_limit_t *limit, + int *family, size_t size); +cap_net_limit_t *cap_net_limit_addr2name(cap_net_limit_t *limit, + const struct sockaddr *sa, socklen_t salen); + +cap_net_limit_t *cap_net_limit_name2addr_family(cap_net_limit_t *limit, + int *family, size_t size); +cap_net_limit_t *cap_net_limit_name2addr(cap_net_limit_t *limit, + const char *name, const char *serv); + +cap_net_limit_t *cap_net_limit_connect(cap_net_limit_t *limit, + const struct sockaddr *sa, socklen_t salen); + +cap_net_limit_t *cap_net_limit_bind(cap_net_limit_t *limit, + const struct sockaddr *sa, socklen_t salen); + +/* Deprecated functions. */ +struct hostent *cap_gethostbyname(cap_channel_t *chan, const char *name); +struct hostent *cap_gethostbyname2(cap_channel_t *chan, const char *name, + int af); +struct hostent *cap_gethostbyaddr(cap_channel_t *chan, const void *addr, + socklen_t len, int af); +#else +/* Capability functions. */ +#define cap_bind(chan, ...) bind(__VA_ARGS__) +#define cap_connect(chan, ...) connect(__VA_ARGS__) +#define cap_getaddrinfo(chan, ...) getaddrinfo(__VA_ARGS__) +#define cap_getnameinfo(chan, ...) getnameinfo(__VA_ARGS__) + +/* Limit functions. */ +#define cap_net_limit_init(chan, ...) ((cap_net_limit_t *)malloc(8)) +#define cap_net_free(...) free(__VA_ARGS__) +static inline int +cap_net_limit(cap_net_limit_t *limit) +{ + free(limit); + return (0); +} + +static inline cap_net_limit_t * +cap_net_limit_addr2name_family(cap_net_limit_t *limit, + int *family __unused, size_t size __unused) +{ + return (limit); +} + +static inline cap_net_limit_t * +cap_net_limit_addr2name(cap_net_limit_t *limit, + const struct sockaddr *sa __unused, socklen_t salen __unused) +{ + return (limit); +} + +static inline cap_net_limit_t * +cap_net_limit_name2addr_family(cap_net_limit_t *limit, + int *family __unused, size_t size __unused) +{ + return (limit); +} + +static inline cap_net_limit_t * +cap_net_limit_name2addr(cap_net_limit_t *limit, + const char *name __unused, const char *serv __unused) +{ + return (limit); +} + +static inline cap_net_limit_t * +cap_net_limit_connect(cap_net_limit_t *limit, + const struct sockaddr *sa __unused, socklen_t salen __unused) +{ + return (limit); +} + +static inline cap_net_limit_t * +cap_net_limit_bind(cap_net_limit_t *limit, + const struct sockaddr *sa __unused, socklen_t salen __unused) +{ + return (limit); +} + +/* Deprecated functions. */ +#define cap_gethostbyname(chan, ...) gethostbyname(__VA_ARGS__) +#define cap_gethostbyname2(chan, ...) gethostbyname2(__VA_ARGS__) +#define cap_gethostbyaddr(chan, ...) gethostbyaddr(__VA_ARGS__) +#endif + +#endif /* !_CAP_NETWORK_H_ */ diff --git a/lib/libcasper/services/cap_net/tests/Makefile b/lib/libcasper/services/cap_net/tests/Makefile new file mode 100644 index 000000000000..5798d7385440 --- /dev/null +++ b/lib/libcasper/services/cap_net/tests/Makefile @@ -0,0 +1,14 @@ +.include <src.opts.mk> + +ATF_TESTS_C= net_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_net +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +WARNS?= 3 + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_net/tests/net_test.c b/lib/libcasper/services/cap_net/tests/net_test.c new file mode 100644 index 000000000000..adf5773233c8 --- /dev/null +++ b/lib/libcasper/services/cap_net/tests/net_test.c @@ -0,0 +1,1487 @@ +/*- + * Copyright (c) 2020 Mariusz Zaborski <oshogbo@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <netdb.h> + +#include <atf-c.h> + +#include <libcasper.h> +#include <casper/cap_net.h> + +#define TEST_DOMAIN_0 "example.com" +#define TEST_DOMAIN_1 "freebsd.org" +#define TEST_IPV4 "1.1.1.1" +#define TEST_IPV6 "2001:4860:4860::8888" +#define TEST_BIND_IPV4 "127.0.0.1" +#define TEST_PORT 80 +#define TEST_PORT_STR "80" + +static cap_channel_t * +create_network_service(void) +{ + cap_channel_t *capcas, *capnet; + + capcas = cap_init(); + ATF_REQUIRE(capcas != NULL); + + capnet = cap_service_open(capcas, "system.net"); + ATF_REQUIRE(capnet != NULL); + + cap_close(capcas); + return (capnet); +} + +static int +test_getnameinfo_v4(cap_channel_t *chan, int family, const char *ip) +{ + struct sockaddr_in ipaddr; + char capfn[MAXHOSTNAMELEN]; + char origfn[MAXHOSTNAMELEN]; + int capret, sysret; + + memset(&ipaddr, 0, sizeof(ipaddr)); + ipaddr.sin_family = family; + inet_pton(family, ip, &ipaddr.sin_addr); + + capret = cap_getnameinfo(chan, (struct sockaddr *)&ipaddr, sizeof(ipaddr), + capfn, sizeof(capfn), NULL, 0, NI_NAMEREQD); + if (capret != 0 && capret == ENOTCAPABLE) + return (ENOTCAPABLE); + + sysret = getnameinfo((struct sockaddr *)&ipaddr, sizeof(ipaddr), origfn, + sizeof(origfn), NULL, 0, NI_NAMEREQD); + if (sysret != 0) { + atf_tc_skip("getnameinfo(%s) failed: %s", + ip, gai_strerror(sysret)); + } + ATF_REQUIRE(capret == 0); + ATF_REQUIRE(strcmp(origfn, capfn) == 0); + + return (0); +} + +static int +test_getnameinfo_v6(cap_channel_t *chan, const char *ip) +{ + struct sockaddr_in6 ipaddr; + char capfn[MAXHOSTNAMELEN]; + char origfn[MAXHOSTNAMELEN]; + int capret, sysret; + + memset(&ipaddr, 0, sizeof(ipaddr)); + ipaddr.sin6_family = AF_INET6; + inet_pton(AF_INET6, ip, &ipaddr.sin6_addr); + + capret = cap_getnameinfo(chan, (struct sockaddr *)&ipaddr, sizeof(ipaddr), + capfn, sizeof(capfn), NULL, 0, NI_NAMEREQD); + if (capret != 0 && capret == ENOTCAPABLE) + return (ENOTCAPABLE); + + sysret = getnameinfo((struct sockaddr *)&ipaddr, sizeof(ipaddr), origfn, + sizeof(origfn), NULL, 0, NI_NAMEREQD); + if (sysret != 0) { + atf_tc_skip("getnameinfo(%s) failed: %s", + ip, gai_strerror(sysret)); + } + ATF_REQUIRE(capret == 0); + ATF_REQUIRE(strcmp(origfn, capfn) == 0); + + return (0); +} + +static int +test_getnameinfo(cap_channel_t *chan, int family, const char *ip) +{ + + if (family == AF_INET6) { + return (test_getnameinfo_v6(chan, ip)); + } + + return (test_getnameinfo_v4(chan, family, ip)); +} + +static int +test_gethostbyaddr_v4(cap_channel_t *chan, int family, const char *ip) +{ + struct in_addr ipaddr; + struct hostent *caphp, *orighp; + + memset(&ipaddr, 0, sizeof(ipaddr)); + inet_pton(AF_INET, ip, &ipaddr); + + caphp = cap_gethostbyaddr(chan, &ipaddr, sizeof(ipaddr), family); + if (caphp == NULL && h_errno == ENOTCAPABLE) + return (ENOTCAPABLE); + + orighp = gethostbyaddr(&ipaddr, sizeof(ipaddr), family); + if (orighp == NULL) + atf_tc_skip("gethostbyaddr(%s) failed", ip); + ATF_REQUIRE(caphp != NULL); + ATF_REQUIRE(strcmp(orighp->h_name, caphp->h_name) == 0); + + return (0); +} + +static int +test_gethostbyaddr_v6(cap_channel_t *chan, const char *ip) +{ + struct in6_addr ipaddr; + struct hostent *caphp, *orighp; + + memset(&ipaddr, 0, sizeof(ipaddr)); + inet_pton(AF_INET6, ip, &ipaddr); + + caphp = cap_gethostbyaddr(chan, &ipaddr, sizeof(ipaddr), AF_INET6); + if (caphp == NULL && h_errno == ENOTCAPABLE) + return (ENOTCAPABLE); + + orighp = gethostbyaddr(&ipaddr, sizeof(ipaddr), AF_INET6); + if (orighp == NULL) + atf_tc_skip("gethostbyaddr(%s) failed", ip); + ATF_REQUIRE(caphp != NULL); + ATF_REQUIRE(strcmp(orighp->h_name, caphp->h_name) == 0); + + return (0); +} + +static int +test_gethostbyaddr(cap_channel_t *chan, int family, const char *ip) +{ + + if (family == AF_INET6) { + return (test_gethostbyaddr_v6(chan, ip)); + } else { + return (test_gethostbyaddr_v4(chan, family, ip)); + } +} + +static int +test_getaddrinfo(cap_channel_t *chan, int family, const char *domain, + const char *servname) +{ + struct addrinfo hints, *capres, *origres, *res0, *res1; + bool found; + int capret, sysret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + + capret = cap_getaddrinfo(chan, domain, servname, &hints, &capres); + if (capret != 0 && capret == ENOTCAPABLE) + return (capret); + + sysret = getaddrinfo(domain, servname, &hints, &origres); + if (sysret != 0) + atf_tc_skip("getaddrinfo(%s) failed: %s", + domain, gai_strerror(sysret)); + ATF_REQUIRE(capret == 0); + + for (res0 = capres; res0 != NULL; res0 = res0->ai_next) { + found = false; + for (res1 = origres; res1 != NULL; res1 = res1->ai_next) { + if (res1->ai_addrlen == res0->ai_addrlen && + memcmp(res1->ai_addr, res0->ai_addr, + res0->ai_addrlen) == 0) { + found = true; + break; + } + } + ATF_REQUIRE(found); + } + + freeaddrinfo(capres); + freeaddrinfo(origres); + return (0); +} + +static int +test_gethostbyname(cap_channel_t *chan, int family, const char *domain) +{ + struct hostent *caphp, *orighp; + + caphp = cap_gethostbyname2(chan, domain, family); + if (caphp == NULL && h_errno == ENOTCAPABLE) + return (h_errno); + + orighp = gethostbyname2(domain, family); + if (orighp == NULL) + atf_tc_skip("gethostbyname2(%s) failed", domain); + + ATF_REQUIRE(caphp != NULL); + ATF_REQUIRE(strcmp(caphp->h_name, orighp->h_name) == 0); + return (0); +} + +static int +test_bind(cap_channel_t *chan, const char *ip) +{ + struct sockaddr_in ipv4; + int capfd, ret, serrno; + + capfd = socket(AF_INET, SOCK_STREAM, 0); + ATF_REQUIRE(capfd > 0); + + memset(&ipv4, 0, sizeof(ipv4)); + ipv4.sin_family = AF_INET; + inet_pton(AF_INET, ip, &ipv4.sin_addr); + + ret = cap_bind(chan, capfd, (struct sockaddr *)&ipv4, sizeof(ipv4)); + serrno = errno; + close(capfd); + + return (ret < 0 ? serrno : 0); +} + +static int +test_connect(cap_channel_t *chan, const char *ip, unsigned short port) +{ + struct sockaddr_in ipv4; + int capfd, ret, serrno; + + capfd = socket(AF_INET, SOCK_STREAM, 0); + ATF_REQUIRE(capfd >= 0); + + memset(&ipv4, 0, sizeof(ipv4)); + ipv4.sin_family = AF_INET; + ipv4.sin_port = htons(port); + inet_pton(AF_INET, ip, &ipv4.sin_addr); + + ret = cap_connect(chan, capfd, (struct sockaddr *)&ipv4, sizeof(ipv4)); + serrno = errno; + ATF_REQUIRE(close(capfd) == 0); + + if (ret < 0 && serrno != ENOTCAPABLE) { + int sd; + + /* + * If the connection failed, it might be because we can't reach + * the destination host. To check, try a plain connect() and + * see if it fails with the same error. + */ + sd = socket(AF_INET, SOCK_STREAM, 0); + ATF_REQUIRE(sd >= 0); + + memset(&ipv4, 0, sizeof(ipv4)); + ipv4.sin_family = AF_INET; + ipv4.sin_port = htons(port); + inet_pton(AF_INET, ip, &ipv4.sin_addr); + ret = connect(sd, (struct sockaddr *)&ipv4, sizeof(ipv4)); + ATF_REQUIRE(ret < 0); + ATF_REQUIRE_MSG(errno == serrno, "errno %d != serrno %d", + errno, serrno); + ATF_REQUIRE(close(sd) == 0); + atf_tc_skip("connect(%s:%d) failed: %s", + ip, port, strerror(serrno)); + } + + return (ret < 0 ? serrno : 0); +} + +static void +test_extend_mode(cap_channel_t *capnet, int current) +{ + cap_net_limit_t *limit; + const int rights[] = { + CAPNET_ADDR2NAME, + CAPNET_NAME2ADDR, + CAPNET_DEPRECATED_ADDR2NAME, + CAPNET_DEPRECATED_NAME2ADDR, + CAPNET_CONNECT, + CAPNET_BIND, + CAPNET_CONNECTDNS + }; + size_t i; + + for (i = 0; i < nitems(rights); i++) { + if (current == rights[i]) + continue; + + limit = cap_net_limit_init(capnet, current | rights[i]); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) != 0); + } +} + +ATF_TC(capnet__getnameinfo); +ATF_TC_HEAD(capnet__getnameinfo, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__getnameinfo, tc) +{ + cap_channel_t *capnet; + + capnet = create_network_service(); + + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0); + + cap_close(capnet); +} + +ATF_TC(capnet__connect); +ATF_TC_HEAD(capnet__connect, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__connect, tc) +{ + cap_channel_t *capnet; + + capnet = create_network_service(); + + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0); + + cap_close(capnet); +} + +ATF_TC(capnet__bind); +ATF_TC_HEAD(capnet__bind, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__bind, tc) +{ + cap_channel_t *capnet; + + capnet = create_network_service(); + + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0); + + cap_close(capnet); +} + +ATF_TC(capnet__getaddrinfo); +ATF_TC_HEAD(capnet__getaddrinfo, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__getaddrinfo, tc) +{ + cap_channel_t *capnet; + struct addrinfo hints, *capres; + + capnet = create_network_service(); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + ATF_REQUIRE(cap_getaddrinfo(capnet, TEST_IPV4, "80", &hints, &capres) == + 0); + + cap_close(capnet); +} + +ATF_TC(capnet__gethostbyname); +ATF_TC_HEAD(capnet__gethostbyname, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__gethostbyname, tc) +{ + cap_channel_t *capnet; + + capnet = create_network_service(); + + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0); + + cap_close(capnet); +} + +ATF_TC(capnet__gethostbyaddr); +ATF_TC_HEAD(capnet__gethostbyaddr, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__gethostbyaddr, tc) +{ + cap_channel_t *capnet; + + capnet = create_network_service(); + + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0); + + cap_close(capnet); +} + +ATF_TC(capnet__getnameinfo_buffer); +ATF_TC_HEAD(capnet__getnameinfo_buffer, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__getnameinfo_buffer, tc) +{ + cap_channel_t *chan; + struct sockaddr_in sin; + int ret; + struct { + char host[sizeof(TEST_IPV4)]; + char host_canary; + char serv[sizeof(TEST_PORT_STR)]; + char serv_canary; + } buffers; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(TEST_PORT); + ret = inet_pton(AF_INET, TEST_IPV4, &sin.sin_addr); + ATF_REQUIRE_EQ(1, ret); + + memset(&buffers, '!', sizeof(buffers)); + + chan = create_network_service(); + ret = cap_getnameinfo(chan, (struct sockaddr *)&sin, sizeof(sin), + buffers.host, sizeof(buffers.host), + buffers.serv, sizeof(buffers.serv), + NI_NUMERICHOST | NI_NUMERICSERV); + ATF_REQUIRE_EQ_MSG(0, ret, "%d", ret); + + // Verify that cap_getnameinfo worked with minimally sized buffers. + ATF_CHECK_EQ(0, strcmp(TEST_IPV4, buffers.host)); + ATF_CHECK_EQ(0, strcmp(TEST_PORT_STR, buffers.serv)); + + // Verify that cap_getnameinfo did not overflow the buffers. + ATF_CHECK_EQ('!', buffers.host_canary); + ATF_CHECK_EQ('!', buffers.serv_canary); + + cap_close(chan); +} + +ATF_TC(capnet__limits_addr2name_mode); +ATF_TC_HEAD(capnet__limits_addr2name_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_addr2name_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE); + + test_extend_mode(capnet, CAPNET_ADDR2NAME); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_addr2name_family); +ATF_TC_HEAD(capnet__limits_addr2name_family, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_addr2name_family, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + int family[] = { AF_INET6, AF_INET }; + + capnet = create_network_service(); + + /* Limit to AF_INET6 and AF_INET. */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name_family(limit, family, nitems(family)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0); + + /* Limit to AF_INET6 and AF_INET. */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name_family(limit, &family[0], 1); + cap_net_limit_addr2name_family(limit, &family[1], 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0); + + /* Limit to AF_INET6. */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name_family(limit, family, 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_addr2name); +ATF_TC_HEAD(capnet__limits_addr2name, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_addr2name, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + struct sockaddr_in ipaddrv4; + struct sockaddr_in6 ipaddrv6; + + capnet = create_network_service(); + + /* Limit to TEST_IPV4 and TEST_IPV6. */ + memset(&ipaddrv4, 0, sizeof(ipaddrv4)); + memset(&ipaddrv6, 0, sizeof(ipaddrv6)); + + ipaddrv4.sin_family = AF_INET; + inet_pton(AF_INET, TEST_IPV4, &ipaddrv4.sin_addr); + + ipaddrv6.sin6_family = AF_INET6; + inet_pton(AF_INET6, TEST_IPV6, &ipaddrv6.sin6_addr); + + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + + cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4, + sizeof(ipaddrv4)); + cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv6, + sizeof(ipaddrv6)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == 0); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, "127.0.0.1") == + ENOTCAPABLE); + + /* Limit to AF_INET. */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4, + sizeof(ipaddrv4)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET6, TEST_IPV6) == + ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, "127.0.0.1") == + ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_ADDR2NAME); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_deprecated_addr2name_mode); +ATF_TC_HEAD(capnet__limits_deprecated_addr2name_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_addr2name_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == + ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_deprecated_addr2name_family); +ATF_TC_HEAD(capnet__limits_deprecated_addr2name_family, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_addr2name_family, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + int family[] = { AF_INET6, AF_INET }; + + capnet = create_network_service(); + + /* Limit to AF_INET6 and AF_INET. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name_family(limit, family, nitems(family)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, PF_LINK, TEST_IPV4) == + ENOTCAPABLE); + + /* Limit to AF_INET6 and AF_INET. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name_family(limit, &family[0], 1); + cap_net_limit_addr2name_family(limit, &family[1], 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, PF_LINK, TEST_IPV4) == + ENOTCAPABLE); + + /* Limit to AF_INET6. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name_family(limit, family, 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, PF_LINK, TEST_IPV4) == + ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_deprecated_addr2name); +ATF_TC_HEAD(capnet__limits_deprecated_addr2name, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_addr2name, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + struct in_addr ipaddrv4; + struct in6_addr ipaddrv6; + + capnet = create_network_service(); + + /* Limit to TEST_IPV4 and TEST_IPV6. */ + memset(&ipaddrv4, 0, sizeof(ipaddrv4)); + memset(&ipaddrv6, 0, sizeof(ipaddrv6)); + + inet_pton(AF_INET, TEST_IPV4, &ipaddrv4); + inet_pton(AF_INET6, TEST_IPV6, &ipaddrv6); + + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + + cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4, + sizeof(ipaddrv4)); + cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv6, + sizeof(ipaddrv6)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, "127.0.0.1") == + ENOTCAPABLE); + + /* Limit to AF_INET. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(limit != NULL); + cap_net_limit_addr2name(limit, (struct sockaddr *)&ipaddrv4, + sizeof(ipaddrv4)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == 0); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET6, TEST_IPV6) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, "127.0.0.1") == + ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_ADDR2NAME); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + + +ATF_TC(capnet__limits_name2addr_mode); +ATF_TC_HEAD(capnet__limits_name2addr_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_name2addr_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + + /* DISALLOWED */ + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE); + + test_extend_mode(capnet, CAPNET_ADDR2NAME); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_name2addr_hosts); +ATF_TC_HEAD(capnet__limits_name2addr_hosts, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_name2addr_hosts, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* Limit to TEST_DOMAIN_0 and localhost only. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr(limit, "localhost", NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, "localhost", NULL) == 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, NULL) == + ENOTCAPABLE); + + /* Limit to TEST_DOMAIN_0 only. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, "localhost", NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + /* Try to extend the limit. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_1, NULL); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_1, NULL); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_name2addr_hosts_servnames_strict); +ATF_TC_HEAD(capnet__limits_name2addr_hosts_servnames_strict, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_name2addr_hosts_servnames_strict, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* + * Limit to TEST_DOMAIN_0 and HTTP service. + */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, "http"); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "http") == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "snmp") == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "http") == + ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_name2addr_hosts_servnames_mix); +ATF_TC_HEAD(capnet__limits_name2addr_hosts_servnames_mix, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_name2addr_hosts_servnames_mix, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* + * Limit to TEST_DOMAIN_0 and any servnamex, and any domain with + * servname HTTP. + */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr(limit, NULL, "http"); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "http") == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "http") == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "snmp") == + ENOTCAPABLE); + + /* Limit to HTTP servname only. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, NULL, "http"); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, "http") == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "http") == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_1, "snmp") == + ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_name2addr_family); +ATF_TC_HEAD(capnet__limits_name2addr_family, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_name2addr_family, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + int family[] = { AF_INET6, AF_INET }; + + capnet = create_network_service(); + + /* Limit to AF_INET and AF_INET6. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr_family(limit, family, nitems(family)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET6, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, PF_LINK, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + + /* Limit to AF_INET and AF_INET6. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr_family(limit, &family[0], 1); + cap_net_limit_name2addr_family(limit, &family[1], 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET6, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, PF_LINK, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + + /* Limit to AF_INET6 only. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr_family(limit, family, 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET6, TEST_DOMAIN_0, NULL) == + 0); + ATF_REQUIRE(test_getaddrinfo(capnet, PF_LINK, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_deprecated_name2addr_mode); +ATF_TC_HEAD(capnet__limits_deprecated_name2addr_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_name2addr_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0); + + /* DISALLOWED */ + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE); + + test_extend_mode(capnet, CAPNET_ADDR2NAME); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_deprecated_name2addr_hosts); +ATF_TC_HEAD(capnet__limits_deprecated_name2addr_hosts, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_name2addr_hosts, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* Limit to TEST_DOMAIN_0 and localhost only. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr(limit, "localhost", NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0); + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, "localhost") == 0); + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_1) == ENOTCAPABLE); + + /* Limit to TEST_DOMAIN_0 only. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, "localhost") == ENOTCAPABLE); + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_1) == ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_deprecated_name2addr_family); +ATF_TC_HEAD(capnet__limits_deprecated_name2addr_family, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_name2addr_family, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + int family[] = { AF_INET6, AF_INET }; + + capnet = create_network_service(); + + /* Limit to AF_INET and AF_INET6. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr_family(limit, family, nitems(family)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0); + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET6, TEST_DOMAIN_0) == 0); + ATF_REQUIRE( + test_gethostbyname(capnet, PF_LINK, TEST_DOMAIN_0) == ENOTCAPABLE); + + /* Limit to AF_INET and AF_INET6. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr_family(limit, &family[0], 1); + cap_net_limit_name2addr_family(limit, &family[1], 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == 0); + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET6, TEST_DOMAIN_0) == 0); + ATF_REQUIRE( + test_gethostbyname(capnet, PF_LINK, TEST_DOMAIN_0) == ENOTCAPABLE); + + /* Limit to AF_INET6 only. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_DOMAIN_0, NULL); + cap_net_limit_name2addr_family(limit, family, 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyname(capnet, AF_INET6, TEST_DOMAIN_0) == 0); + ATF_REQUIRE( + test_gethostbyname(capnet, PF_LINK, TEST_DOMAIN_0) == ENOTCAPABLE); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_bind_mode); +ATF_TC_HEAD(capnet__limits_bind_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_bind_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_BIND); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0); + + /* DISALLOWED */ + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == ENOTCAPABLE); + + test_extend_mode(capnet, CAPNET_ADDR2NAME); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_bind); +ATF_TC_HEAD(capnet__limits_bind, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_bind, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + struct sockaddr_in ipv4; + + capnet = create_network_service(); + + limit = cap_net_limit_init(capnet, CAPNET_BIND); + ATF_REQUIRE(limit != NULL); + + memset(&ipv4, 0, sizeof(ipv4)); + ipv4.sin_family = AF_INET; + inet_pton(AF_INET, TEST_BIND_IPV4, &ipv4.sin_addr); + + cap_net_limit_bind(limit, (struct sockaddr *)&ipv4, sizeof(ipv4)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == 0); + ATF_REQUIRE(test_bind(capnet, "127.0.0.2") == ENOTCAPABLE); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_connect_mode); +ATF_TC_HEAD(capnet__limits_connect_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_connect_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_CONNECT); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0); + + /* DISALLOWED */ + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE); + + test_extend_mode(capnet, CAPNET_ADDR2NAME); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_connect_dns_mode); +ATF_TC_HEAD(capnet__limits_connect_dns_mode, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_connect_dns_mode, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + + capnet = create_network_service(); + + /* LIMIT */ + limit = cap_net_limit_init(capnet, CAPNET_CONNECT | CAPNET_CONNECTDNS); + ATF_REQUIRE(limit != NULL); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + /* ALLOWED */ + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0); + + /* DISALLOWED */ + ATF_REQUIRE( + test_gethostbyname(capnet, AF_INET, TEST_DOMAIN_0) == ENOTCAPABLE); + ATF_REQUIRE(test_getnameinfo(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_gethostbyaddr(capnet, AF_INET, TEST_IPV4) == + ENOTCAPABLE); + ATF_REQUIRE(test_getaddrinfo(capnet, AF_INET, TEST_DOMAIN_0, NULL) == + ENOTCAPABLE); + ATF_REQUIRE(test_bind(capnet, TEST_BIND_IPV4) == ENOTCAPABLE); + + test_extend_mode(capnet, CAPNET_ADDR2NAME); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_connect); +ATF_TC_HEAD(capnet__limits_connect, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_connect, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + struct sockaddr_in ipv4; + + capnet = create_network_service(); + + /* Limit only to TEST_IPV4 on port 80 and 443. */ + limit = cap_net_limit_init(capnet, CAPNET_CONNECT); + ATF_REQUIRE(limit != NULL); + memset(&ipv4, 0, sizeof(ipv4)); + ipv4.sin_family = AF_INET; + ipv4.sin_port = htons(80); + inet_pton(AF_INET, TEST_IPV4, &ipv4.sin_addr); + cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4)); + + ipv4.sin_port = htons(443); + cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 80) == 0); + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 80) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 443) == 0); + + /* Limit only to TEST_IPV4 on port 443. */ + limit = cap_net_limit_init(capnet, CAPNET_CONNECT); + cap_net_limit_connect(limit, (struct sockaddr *)&ipv4, sizeof(ipv4)); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 433) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 80) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE); + ATF_REQUIRE(test_connect(capnet, TEST_IPV4, 443) == 0); + + /* Unable to set empty limits. Empty limits means full access. */ + limit = cap_net_limit_init(capnet, CAPNET_CONNECT); + ATF_REQUIRE(cap_net_limit(limit) != 0); + + cap_close(capnet); +} + +ATF_TC(capnet__limits_connecttodns); +ATF_TC_HEAD(capnet__limits_connecttodns, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_connecttodns, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + struct addrinfo hints, *capres, *res; + int family[] = { AF_INET }; + int error; + + capnet = create_network_service(); + + limit = cap_net_limit_init(capnet, CAPNET_CONNECTDNS | + CAPNET_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_IPV4, "80"); + cap_net_limit_name2addr_family(limit, family, 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE); + ATF_REQUIRE(cap_getaddrinfo(capnet, TEST_IPV4, "80", &hints, &capres) == + 0); + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE); + + for (res = capres; res != NULL; res = res->ai_next) { + int s; + + ATF_REQUIRE(res->ai_family == AF_INET); + ATF_REQUIRE(res->ai_socktype == SOCK_STREAM); + + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + ATF_REQUIRE(s >= 0); + + error = cap_connect(capnet, s, res->ai_addr, + res->ai_addrlen); + if (error != 0 && errno != ENOTCAPABLE) + atf_tc_skip("unable to connect: %s", strerror(errno)); + ATF_REQUIRE(error == 0); + ATF_REQUIRE(close(s) == 0); + } + + freeaddrinfo(capres); + cap_close(capnet); +} + + +ATF_TC(capnet__limits_deprecated_connecttodns); +ATF_TC_HEAD(capnet__limits_deprecated_connecttodns, tc) +{ + atf_tc_set_md_var(tc, "require.config", "allow_network_access"); +} +ATF_TC_BODY(capnet__limits_deprecated_connecttodns, tc) +{ + cap_channel_t *capnet; + cap_net_limit_t *limit; + struct hostent *caphp; + struct in_addr ipaddr; + struct sockaddr_in connaddr; + int family[] = { AF_INET }; + int error, i; + + capnet = create_network_service(); + + limit = cap_net_limit_init(capnet, CAPNET_CONNECTDNS | + CAPNET_DEPRECATED_NAME2ADDR); + ATF_REQUIRE(limit != NULL); + cap_net_limit_name2addr(limit, TEST_IPV4, NULL); + cap_net_limit_name2addr_family(limit, family, 1); + ATF_REQUIRE(cap_net_limit(limit) == 0); + + memset(&ipaddr, 0, sizeof(ipaddr)); + inet_pton(AF_INET, TEST_IPV4, &ipaddr); + + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE); + caphp = cap_gethostbyname2(capnet, TEST_IPV4, AF_INET); + ATF_REQUIRE(caphp != NULL); + ATF_REQUIRE(caphp->h_addrtype == AF_INET); + ATF_REQUIRE(test_connect(capnet, "8.8.8.8", 433) == ENOTCAPABLE); + + for (i = 0; caphp->h_addr_list[i] != NULL; i++) { + int s; + + s = socket(AF_INET, SOCK_STREAM, 0); + ATF_REQUIRE(s >= 0); + + memset(&connaddr, 0, sizeof(connaddr)); + connaddr.sin_family = AF_INET; + memcpy((char *)&connaddr.sin_addr.s_addr, + (char *)caphp->h_addr_list[i], caphp->h_length); + connaddr.sin_port = htons(80); + + error = cap_connect(capnet, s, (struct sockaddr *)&connaddr, + sizeof(connaddr)); + if (error != 0 && errno != ENOTCAPABLE) + atf_tc_skip("unable to connect: %s", strerror(errno)); + ATF_REQUIRE(error == 0); + ATF_REQUIRE(close(s) == 0); + } + + cap_close(capnet); +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, capnet__connect); + ATF_TP_ADD_TC(tp, capnet__bind); + ATF_TP_ADD_TC(tp, capnet__getnameinfo); + ATF_TP_ADD_TC(tp, capnet__getaddrinfo); + ATF_TP_ADD_TC(tp, capnet__gethostbyname); + ATF_TP_ADD_TC(tp, capnet__gethostbyaddr); + + ATF_TP_ADD_TC(tp, capnet__getnameinfo_buffer); + + ATF_TP_ADD_TC(tp, capnet__limits_addr2name_mode); + ATF_TP_ADD_TC(tp, capnet__limits_addr2name_family); + ATF_TP_ADD_TC(tp, capnet__limits_addr2name); + + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_addr2name_mode); + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_addr2name_family); + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_addr2name); + + ATF_TP_ADD_TC(tp, capnet__limits_name2addr_mode); + ATF_TP_ADD_TC(tp, capnet__limits_name2addr_hosts); + ATF_TP_ADD_TC(tp, capnet__limits_name2addr_hosts_servnames_strict); + ATF_TP_ADD_TC(tp, capnet__limits_name2addr_hosts_servnames_mix); + ATF_TP_ADD_TC(tp, capnet__limits_name2addr_family); + + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_name2addr_mode); + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_name2addr_hosts); + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_name2addr_family); + + ATF_TP_ADD_TC(tp, capnet__limits_bind_mode); + ATF_TP_ADD_TC(tp, capnet__limits_bind); + + ATF_TP_ADD_TC(tp, capnet__limits_connect_mode); + ATF_TP_ADD_TC(tp, capnet__limits_connect_dns_mode); + ATF_TP_ADD_TC(tp, capnet__limits_connect); + + ATF_TP_ADD_TC(tp, capnet__limits_connecttodns); + ATF_TP_ADD_TC(tp, capnet__limits_deprecated_connecttodns); + + return (atf_no_error()); +} diff --git a/lib/libcasper/services/cap_netdb/Makefile b/lib/libcasper/services/cap_netdb/Makefile new file mode 100644 index 000000000000..853052e78d04 --- /dev/null +++ b/lib/libcasper/services/cap_netdb/Makefile @@ -0,0 +1,33 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_netdb + +SRCS= cap_netdb.c +.endif + +INCS= cap_netdb.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_netdb.3 + +MLINKS+=cap_netdb.3 libcap_netdb.3 +MLINKS+=cap_netdb.3 cap_getprotobyname.3 + +.include <bsd.lib.mk> + +# GCC 13 complains incorrectly about free after failed realloc: GCC bug #110501 +CFLAGS.cap_netdb.c+= ${NO_WUSE_AFTER_FREE} diff --git a/lib/libcasper/services/cap_netdb/Makefile.depend b/lib/libcasper/services/cap_netdb/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_netdb/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_netdb/cap_netdb.3 b/lib/libcasper/services/cap_netdb/cap_netdb.3 new file mode 100644 index 000000000000..1f587c2057e7 --- /dev/null +++ b/lib/libcasper/services/cap_netdb/cap_netdb.3 @@ -0,0 +1,91 @@ +.\" Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org> +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt CAP_NETDB 3 +.Os +.Sh NAME +.Nm cap_getprotobyname , +.Nd "library for getting network proto entry in capability mode" +.Sh LIBRARY +.Lb libcap_netdb +.Sh SYNOPSIS +.In sys/nv.h +.In libcasper.h +.In casper/cap_netdb.h +.Ft "struct protoent *" +.Fn cap_getprotobyname "const cap_channel_t *chan" "const char *name" +.Sh DESCRIPTION +The function +.Fn cap_getprotobyname +is equivalent to +.Xr getprotobyname 3 +except that the connection to the +.Nm system.netdb +service needs to be provided. +It is reentrant but not thread-safe. +That is, it may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh EXAMPLES +The following example first opens a capability to casper and then uses this +capability to create the +.Nm system.netdb +casper service and uses it to look up a protocol by name. +.Bd -literal +cap_channel_t *capcas, *capnetdb; +struct protoent *ent; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Enter capability mode sandbox. */ +if (caph_enter() < 0) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.netdb service. */ +capnetdb = cap_service_open(capcas, "system.netdb"); +if (capnetdb == NULL) + err(1, "Unable to open system.netdb service"); + +/* Close Casper capability, we don't need it anymore. */ +cap_close(capcas); + +ent = cap_getprotobyname(capnetdb, "http"); +if (ent == NULL) + errx(1, "cap_getprotobyname failed to find http proto"); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr caph_enter 3 , +.Xr err 3 , +.Xr getprotobyname 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh AUTHORS +The +.Nm cap_netdb +service was implemented by +.An Ryan Moeller Aq Mt freqlabs@FreeBSD.org . diff --git a/lib/libcasper/services/cap_netdb/cap_netdb.c b/lib/libcasper/services/cap_netdb/cap_netdb.c new file mode 100644 index 000000000000..7f46f9b4ff40 --- /dev/null +++ b/lib/libcasper/services/cap_netdb/cap_netdb.c @@ -0,0 +1,153 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/dnv.h> +#include <sys/nv.h> +#include <netinet/in.h> + +#include <assert.h> +#include <errno.h> +#include <netdb.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_netdb.h" + +static struct protoent * +protoent_unpack(nvlist_t *nvl) +{ + struct protoent *pp; + char **aliases; + size_t n; + + pp = malloc(sizeof(*pp)); + if (pp == NULL) { + nvlist_destroy(nvl); + return (NULL); + } + + pp->p_name = nvlist_take_string(nvl, "name"); + + aliases = nvlist_take_string_array(nvl, "aliases", &n); + pp->p_aliases = realloc(aliases, sizeof(char *) * (n + 1)); + if (pp->p_aliases == NULL) { + while (n-- > 0) + free(aliases[n]); + free(aliases); + free(pp->p_name); + free(pp); + nvlist_destroy(nvl); + return (NULL); + } + pp->p_aliases[n] = NULL; + + pp->p_proto = (int)nvlist_take_number(nvl, "proto"); + + nvlist_destroy(nvl); + return (pp); +} + +struct protoent * +cap_getprotobyname(cap_channel_t *chan, const char *name) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "getprotobyname"); + nvlist_add_string(nvl, "name", name); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (NULL); + if (dnvlist_get_number(nvl, "error", 0) != 0) { + nvlist_destroy(nvl); + return (NULL); + } + return (protoent_unpack(nvl)); +} + +static void +protoent_pack(const struct protoent *pp, nvlist_t *nvl) +{ + int n = 0; + + nvlist_add_string(nvl, "name", pp->p_name); + + while (pp->p_aliases[n] != NULL) + ++n; + nvlist_add_string_array(nvl, "aliases", + (const char * const *)pp->p_aliases, n); + + nvlist_add_number(nvl, "proto", (uint64_t)pp->p_proto); +} + +static int +netdb_getprotobyname(const nvlist_t *limits __unused, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + const char *name; + struct protoent *pp; + + name = dnvlist_get_string(nvlin, "name", NULL); + if (name == NULL) + return (EDOOFUS); + + pp = getprotobyname(name); + if (pp == NULL) + return (EINVAL); + + protoent_pack(pp, nvlout); + return (0); +} + +static int +netdb_limit(const nvlist_t *oldlimits __unused, + const nvlist_t *newlimits __unused) +{ + + return (0); +} + +static int +netdb_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (strcmp(cmd, "getprotobyname") == 0) + error = netdb_getprotobyname(limits, nvlin, nvlout); + else + error = NO_RECOVERY; + + return (error); +} + +CREATE_SERVICE("system.netdb", netdb_limit, netdb_command, 0); diff --git a/lib/libcasper/services/cap_netdb/cap_netdb.h b/lib/libcasper/services/cap_netdb/cap_netdb.h new file mode 100644 index 000000000000..4a89797e33cc --- /dev/null +++ b/lib/libcasper/services/cap_netdb/cap_netdb.h @@ -0,0 +1,47 @@ +/*- + * Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_NETDB_H_ +#define _CAP_NETDB_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/cdefs.h> + +#include <netdb.h> + +#ifdef WITH_CASPER +__BEGIN_DECLS + +struct protoent *cap_getprotobyname(cap_channel_t *chan, const char *name); + +__END_DECLS +#else +#define cap_getprotobyname(chan, name) getprotobyname(name) +#endif + +#endif /* !_CAP_NETDB_H_ */ diff --git a/lib/libcasper/services/cap_netdb/tests/Makefile b/lib/libcasper/services/cap_netdb/tests/Makefile new file mode 100644 index 000000000000..bc5e04578323 --- /dev/null +++ b/lib/libcasper/services/cap_netdb/tests/Makefile @@ -0,0 +1,12 @@ +.include <src.opts.mk> + +ATF_TESTS_C= netdb_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_netdb +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_netdb/tests/netdb_test.c b/lib/libcasper/services/cap_netdb/tests/netdb_test.c new file mode 100644 index 000000000000..5b984a8f6bd2 --- /dev/null +++ b/lib/libcasper/services/cap_netdb/tests/netdb_test.c @@ -0,0 +1,91 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Ryan Moeller <freqlabs@FreeBSD.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/capsicum.h> +#include <sys/nv.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <casper/cap_netdb.h> + +#include <atf-c.h> + +static cap_channel_t * +initcap(void) +{ + cap_channel_t *capcas, *capnetdb; + + capcas = cap_init(); + ATF_REQUIRE(capcas != NULL); + + capnetdb = cap_service_open(capcas, "system.netdb"); + ATF_REQUIRE(capnetdb != NULL); + + cap_close(capcas); + + return (capnetdb); +} + +ATF_TC_WITHOUT_HEAD(cap_netdb__getprotobyname); +ATF_TC_BODY(cap_netdb__getprotobyname, tc) +{ + cap_channel_t *capnetdb; + struct protoent *pp; + size_t n = 0; + + capnetdb = initcap(); + + pp = cap_getprotobyname(capnetdb, "tcp"); + ATF_REQUIRE(pp != NULL); + + ATF_REQUIRE(pp->p_name != NULL); + ATF_REQUIRE(pp->p_aliases != NULL); + while (pp->p_aliases[n] != NULL) + ++n; + ATF_REQUIRE(n > 0); + ATF_REQUIRE(pp->p_proto != 0); + + cap_close(capnetdb); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, cap_netdb__getprotobyname); + + return (atf_no_error()); +} diff --git a/lib/libcasper/services/cap_pwd/Makefile b/lib/libcasper/services/cap_pwd/Makefile new file mode 100644 index 000000000000..a1e97845c736 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/Makefile @@ -0,0 +1,44 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_pwd + +SRCS= cap_pwd.c +.endif + +INCS= cap_pwd.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_pwd.3 + +MLINKS+=cap_pwd.3 libcap_pwd.3 +MLINKS+=cap_pwd.3 cap_getpwent.3 +MLINKS+=cap_pwd.3 cap_getpwnam.3 +MLINKS+=cap_pwd.3 cap_getpwuid.3 +MLINKS+=cap_pwd.3 cap_getpwent_r.3 +MLINKS+=cap_pwd.3 cap_getpwnam_r.3 +MLINKS+=cap_pwd.3 cap_getpwuid_r.3 +MLINKS+=cap_pwd.3 cap_setpassent.3 +MLINKS+=cap_pwd.3 cap_setpwent.3 +MLINKS+=cap_pwd.3 cap_endpwent.3 +MLINKS+=cap_pwd.3 cap_pwd_limit_cmds.3 +MLINKS+=cap_pwd.3 cap_pwd_limit_fields.3 +MLINKS+=cap_pwd.3 cap_pwd_limit_users.3 + +.include <bsd.lib.mk> + +# GCC 13 complains incorrectly about free after failed realloc: GCC bug #110501 +CFLAGS.cap_pwd.c+= ${NO_WUSE_AFTER_FREE} diff --git a/lib/libcasper/services/cap_pwd/Makefile.depend b/lib/libcasper/services/cap_pwd/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_pwd/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_pwd/cap_pwd.3 b/lib/libcasper/services/cap_pwd/cap_pwd.3 new file mode 100644 index 000000000000..b66a0cd083ba --- /dev/null +++ b/lib/libcasper/services/cap_pwd/cap_pwd.3 @@ -0,0 +1,242 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt CAP_PWD 3 +.Os +.Sh NAME +.Nm cap_getpwent , +.Nm cap_getpwnam , +.Nm cap_getpwuid , +.Nm cap_getpwent_r , +.Nm cap_getpwnam_r , +.Nm cap_getpwuid_r , +.Nm cap_setpassent , +.Nm cap_setpwent , +.Nm cap_endpwent , +.Nm cap_pwd_limit_cmds , +.Nm cap_pwd_limit_fields , +.Nm cap_pwd_limit_users +.Nd "library for password database operations in capability mode" +.Sh LIBRARY +.Lb libcap_pwd +.Sh SYNOPSIS +.In libcasper.h +.In casper/cap_pwd.h +.Ft struct passwd * +.Fn cap_getpwent "cap_channel_t *chan" +.Ft struct passwd * +.Fn cap_getpwnam "cap_channel_t *chan" "const char *login" +.Ft struct passwd * +.Fn cap_getpwuid "cap_channel_t *chan" "uid_t uid" +.Ft int +.Fn cap_getpwent_r "cap_channel_t *chan" "struct passwd *pwd" "char *buffer" "size_t bufsize" "struct passwd **result" +.Ft int +.Fn cap_getpwnam_r "cap_channel_t *chan" "const char *name" "struct passwd *pwd" "char *buffer" "size_t bufsize" "struct passwd **result" +.Ft int +.Fn cap_getpwuid_r "cap_channel_t *chan" "uid_t uid" "struct passwd *pwd" "char *buffer" "size_t bufsize" "struct passwd **result" +.Ft int +.Fn cap_setpassent "cap_channel_t *chan" "int stayopen" +.Ft void +.Fn cap_setpwent "cap_channel_t *chan" +.Ft void +.Fn cap_endpwent "cap_channel_t *chan" +.Ft int +.Fn cap_pwd_limit_cmds "cap_channel_t *chan" "const char * const *cmds" "size_t ncmds" +.Ft int +.Fn cap_pwd_limit_fields "cap_channel_t *chan" "const char * const *fields" "size_t nfields" +.Ft int +.Fn cap_pwd_limit_users "cap_channel_t *chan" "const char * const *names" "size_t nnames" "uid_t *uids" "size_t nuids" +.Sh DESCRIPTION +The functions +.Fn cap_getpwent , +.Fn cap_getpwnam , +.Fn cap_getpwuid , +.Fn cap_getpwent_r , +.Fn cap_getpwnam_r , +.Fn cap_getpwuid_r , +.Fn cap_setpassent , +.Fn cap_setpwent , +and +.Fn cap_endpwent +are respectively equivalent to +.Xr getpwent 3 , +.Xr getpwnam 3 , +.Xr getpwuid 3 , +.Xr getpwent_r 3 , +.Xr getpwnam_r 3 , +.Xr getpwuid_r 3 , +.Xr setpassent 3 , +.Xr setpwent 3 , +and +.Xr cap_endpwent 3 +except that the connection to the +.Nm system.pwd +service needs to be provided. +.Pp +The +.Fn cap_pwd_limit_cmds +function limits the functions allowed in the service. +The +.Fa cmds +variable can be set to +.Dv getpwent , +.Dv getpwnam , +.Dv getpwuid , +.Dv getpwent_r , +.Dv getpwnam_r , +.Dv getpwuid_r , +.Dv setpassent , +.Dv setpwent , +or +.Dv endpwent +which will allow to use the function associated with the name. +The +.Fa ncmds +variable contains the number of +.Fa cmds +provided. +.Pp +The +.Fn cap_pwd_limit_fields +function allows limit fields returned in the structure +.Vt passwd . +The +.Fa fields +variable can be set to +.Dv pw_name , +.Dv pw_passwd , +.Dv pw_uid , +.Dv pw_gid , +.Dv pw_change , +.Dv pw_class , +.Dv pw_gecos , +.Dv pw_dir , +.Dv pw_shell , +.Dv pw_expire +or +.Dv pw_fields +The field which was set as the limit will be returned, while the rest of the +values not set this way will have default values. +The +.Fa nfields +variable contains the number of +.Fa fields +provided. +.Pp +The +.Fn cap_pwd_limit_users +function allows to limit access to users. +The +.Fa names +variable allows to limit users by name and the +.Fa uids +variable by the user number. +The +.Fa nnames +and +.Fa nuids +variables provide numbers of limited names and uids. +.Pp +All of these functions are reentrant but not thread-safe. +That is, they may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh EXAMPLES +The following example first opens a capability to casper and then uses this +capability to create the +.Nm system.pwd +casper service and uses it to get a user name. +.Bd -literal +cap_channel_t *capcas, *cappwd; +const char *cmds[] = { "getpwuid" }; +const char *fields[] = { "pw_name" }; +uid_t uid[] = { 1 }; +struct passwd *passwd; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Enter capability mode sandbox. */ +if (cap_enter() < 0 && errno != ENOSYS) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.pwd service. */ +cappwd = cap_service_open(capcas, "system.pwd"); +if (cappwd == NULL) + err(1, "Unable to open system.pwd service"); + +/* Close Casper capability, we don't need it anymore. */ +cap_close(capcas); + +/* Limit service to one single function. */ +if (cap_pwd_limit_cmds(cappwd, cmds, nitems(cmds))) + err(1, "Unable to limit access to system.pwd service"); + +/* Limit service to one field as we only need name of the user. */ +if (cap_pwd_limit_fields(cappwd, fields, nitems(fields))) + err(1, "Unable to limit access to system.pwd service"); + +/* Limit service to one uid. */ +if (cap_pwd_limit_users(cappwd, NULL, 0, uid, nitems(uid))) + err(1, "Unable to limit access to system.pwd service"); + +passwd = cap_getpwuid(cappwd, uid[0]); +if (passwd == NULL) + err(1, "Unable to get name of user"); + +printf("UID %d is associated with name %s.\\n", uid[0], passwd->pw_name); + +cap_close(cappwd); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr endpwent 3 , +.Xr err 3 , +.Xr getpwent 3 , +.Xr getpwent_r 3 , +.Xr getpwnam 3 , +.Xr getpwnam_r 3 , +.Xr getpwuid 3 , +.Xr getpwuid_r 3 , +.Xr setpassent 3 , +.Xr setpwent 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm cap_pwd +service first appeared in +.Fx 10.3 . +.Sh AUTHORS +The +.Nm cap_pwd +service was implemented by +.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net +under sponsorship from the FreeBSD Foundation. +.Pp +This manual page was written by +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org . diff --git a/lib/libcasper/services/cap_pwd/cap_pwd.c b/lib/libcasper/services/cap_pwd/cap_pwd.c new file mode 100644 index 000000000000..1b5963d8be3d --- /dev/null +++ b/lib/libcasper/services/cap_pwd/cap_pwd.c @@ -0,0 +1,781 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/nv.h> + +#include <assert.h> +#include <errno.h> +#include <pwd.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_pwd.h" + +static struct passwd gpwd; +static char *gbuffer; +static size_t gbufsize; + +static int +passwd_resize(void) +{ + char *buf; + + if (gbufsize == 0) + gbufsize = 1024; + else + gbufsize *= 2; + + buf = gbuffer; + gbuffer = realloc(buf, gbufsize); + if (gbuffer == NULL) { + free(buf); + gbufsize = 0; + return (ENOMEM); + } + memset(gbuffer, 0, gbufsize); + + return (0); +} + +static int +passwd_unpack_string(const nvlist_t *nvl, const char *fieldname, char **fieldp, + char **bufferp, size_t *bufsizep) +{ + const char *str; + size_t len; + + str = nvlist_get_string(nvl, fieldname); + len = strlcpy(*bufferp, str, *bufsizep); + if (len >= *bufsizep) + return (ERANGE); + *fieldp = *bufferp; + *bufferp += len + 1; + *bufsizep -= len + 1; + + return (0); +} + +static int +passwd_unpack(const nvlist_t *nvl, struct passwd *pwd, char *buffer, + size_t bufsize) +{ + int error; + + if (!nvlist_exists_string(nvl, "pw_name")) + return (EINVAL); + + explicit_bzero(pwd, sizeof(*pwd)); + + error = passwd_unpack_string(nvl, "pw_name", &pwd->pw_name, &buffer, + &bufsize); + if (error != 0) + return (error); + pwd->pw_uid = (uid_t)nvlist_get_number(nvl, "pw_uid"); + pwd->pw_gid = (gid_t)nvlist_get_number(nvl, "pw_gid"); + pwd->pw_change = (time_t)nvlist_get_number(nvl, "pw_change"); + error = passwd_unpack_string(nvl, "pw_passwd", &pwd->pw_passwd, &buffer, + &bufsize); + if (error != 0) + return (error); + error = passwd_unpack_string(nvl, "pw_class", &pwd->pw_class, &buffer, + &bufsize); + if (error != 0) + return (error); + error = passwd_unpack_string(nvl, "pw_gecos", &pwd->pw_gecos, &buffer, + &bufsize); + if (error != 0) + return (error); + error = passwd_unpack_string(nvl, "pw_dir", &pwd->pw_dir, &buffer, + &bufsize); + if (error != 0) + return (error); + error = passwd_unpack_string(nvl, "pw_shell", &pwd->pw_shell, &buffer, + &bufsize); + if (error != 0) + return (error); + pwd->pw_expire = (time_t)nvlist_get_number(nvl, "pw_expire"); + pwd->pw_fields = (int)nvlist_get_number(nvl, "pw_fields"); + + return (0); +} + +static int +cap_getpwcommon_r(cap_channel_t *chan, const char *cmd, const char *login, + uid_t uid, struct passwd *pwd, char *buffer, size_t bufsize, + struct passwd **result) +{ + nvlist_t *nvl; + bool getpw_r; + int error; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", cmd); + if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0) { + /* Add nothing. */ + } else if (strcmp(cmd, "getpwnam") == 0 || + strcmp(cmd, "getpwnam_r") == 0) { + nvlist_add_string(nvl, "name", login); + } else if (strcmp(cmd, "getpwuid") == 0 || + strcmp(cmd, "getpwuid_r") == 0) { + nvlist_add_number(nvl, "uid", (uint64_t)uid); + } else { + abort(); + } + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + assert(errno != 0); + *result = NULL; + return (errno); + } + error = (int)nvlist_get_number(nvl, "error"); + if (error != 0) { + nvlist_destroy(nvl); + *result = NULL; + return (error); + } + + if (!nvlist_exists_string(nvl, "pw_name")) { + /* Not found. */ + nvlist_destroy(nvl); + *result = NULL; + return (0); + } + + getpw_r = (strcmp(cmd, "getpwent_r") == 0 || + strcmp(cmd, "getpwnam_r") == 0 || strcmp(cmd, "getpwuid_r") == 0); + + for (;;) { + error = passwd_unpack(nvl, pwd, buffer, bufsize); + if (getpw_r || error != ERANGE) + break; + assert(buffer == gbuffer); + assert(bufsize == gbufsize); + error = passwd_resize(); + if (error != 0) + break; + /* Update pointers after resize. */ + buffer = gbuffer; + bufsize = gbufsize; + } + + nvlist_destroy(nvl); + + if (error == 0) + *result = pwd; + else + *result = NULL; + + return (error); +} + +static struct passwd * +cap_getpwcommon(cap_channel_t *chan, const char *cmd, const char *login, + uid_t uid) +{ + struct passwd *result; + int error, serrno; + + serrno = errno; + + error = cap_getpwcommon_r(chan, cmd, login, uid, &gpwd, gbuffer, + gbufsize, &result); + if (error != 0) { + errno = error; + return (NULL); + } + + errno = serrno; + + return (result); +} + +struct passwd * +cap_getpwent(cap_channel_t *chan) +{ + + return (cap_getpwcommon(chan, "getpwent", NULL, 0)); +} + +struct passwd * +cap_getpwnam(cap_channel_t *chan, const char *login) +{ + + return (cap_getpwcommon(chan, "getpwnam", login, 0)); +} + +struct passwd * +cap_getpwuid(cap_channel_t *chan, uid_t uid) +{ + + return (cap_getpwcommon(chan, "getpwuid", NULL, uid)); +} + +int +cap_getpwent_r(cap_channel_t *chan, struct passwd *pwd, char *buffer, + size_t bufsize, struct passwd **result) +{ + + return (cap_getpwcommon_r(chan, "getpwent_r", NULL, 0, pwd, buffer, + bufsize, result)); +} + +int +cap_getpwnam_r(cap_channel_t *chan, const char *name, struct passwd *pwd, + char *buffer, size_t bufsize, struct passwd **result) +{ + + return (cap_getpwcommon_r(chan, "getpwnam_r", name, 0, pwd, buffer, + bufsize, result)); +} + +int +cap_getpwuid_r(cap_channel_t *chan, uid_t uid, struct passwd *pwd, char *buffer, + size_t bufsize, struct passwd **result) +{ + + return (cap_getpwcommon_r(chan, "getpwuid_r", NULL, uid, pwd, buffer, + bufsize, result)); +} + +int +cap_setpassent(cap_channel_t *chan, int stayopen) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "setpassent"); + nvlist_add_bool(nvl, "stayopen", stayopen != 0); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (0); + if (nvlist_get_number(nvl, "error") != 0) { + errno = nvlist_get_number(nvl, "error"); + nvlist_destroy(nvl); + return (0); + } + nvlist_destroy(nvl); + + return (1); +} + +static void +cap_set_end_pwent(cap_channel_t *chan, const char *cmd) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", cmd); + /* Ignore any errors, we have no way to report them. */ + nvlist_destroy(cap_xfer_nvlist(chan, nvl)); +} + +void +cap_setpwent(cap_channel_t *chan) +{ + + cap_set_end_pwent(chan, "setpwent"); +} + +void +cap_endpwent(cap_channel_t *chan) +{ + + cap_set_end_pwent(chan, "endpwent"); +} + +int +cap_pwd_limit_cmds(cap_channel_t *chan, const char * const *cmds, size_t ncmds) +{ + nvlist_t *limits, *nvl; + unsigned int i; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) { + limits = nvlist_create(0); + } else { + if (nvlist_exists_nvlist(limits, "cmds")) + nvlist_free_nvlist(limits, "cmds"); + } + nvl = nvlist_create(0); + for (i = 0; i < ncmds; i++) + nvlist_add_null(nvl, cmds[i]); + nvlist_move_nvlist(limits, "cmds", nvl); + return (cap_limit_set(chan, limits)); +} + +int +cap_pwd_limit_fields(cap_channel_t *chan, const char * const *fields, + size_t nfields) +{ + nvlist_t *limits, *nvl; + unsigned int i; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) { + limits = nvlist_create(0); + } else { + if (nvlist_exists_nvlist(limits, "fields")) + nvlist_free_nvlist(limits, "fields"); + } + nvl = nvlist_create(0); + for (i = 0; i < nfields; i++) + nvlist_add_null(nvl, fields[i]); + nvlist_move_nvlist(limits, "fields", nvl); + return (cap_limit_set(chan, limits)); +} + +int +cap_pwd_limit_users(cap_channel_t *chan, const char * const *names, + size_t nnames, uid_t *uids, size_t nuids) +{ + nvlist_t *limits, *users; + char nvlname[64]; + unsigned int i; + int n; + + if (cap_limit_get(chan, &limits) < 0) + return (-1); + if (limits == NULL) { + limits = nvlist_create(0); + } else { + if (nvlist_exists_nvlist(limits, "users")) + nvlist_free_nvlist(limits, "users"); + } + users = nvlist_create(0); + for (i = 0; i < nuids; i++) { + n = snprintf(nvlname, sizeof(nvlname), "uid%u", i); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_number(users, nvlname, (uint64_t)uids[i]); + } + for (i = 0; i < nnames; i++) { + n = snprintf(nvlname, sizeof(nvlname), "name%u", i); + assert(n > 0 && n < (int)sizeof(nvlname)); + nvlist_add_string(users, nvlname, names[i]); + } + nvlist_move_nvlist(limits, "users", users); + return (cap_limit_set(chan, limits)); +} + + +/* + * Service functions. + */ +static bool +pwd_allowed_cmd(const nvlist_t *limits, const char *cmd) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed commands, then all commands + * are allowed. + */ + if (!nvlist_exists_nvlist(limits, "cmds")) + return (true); + + limits = nvlist_get_nvlist(limits, "cmds"); + return (nvlist_exists_null(limits, cmd)); +} + +static int +pwd_allowed_cmds(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!pwd_allowed_cmd(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +pwd_allowed_user(const nvlist_t *limits, const char *uname, uid_t uid) +{ + const char *name; + void *cookie; + int type; + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed users, then all users are allowed. + */ + if (!nvlist_exists_nvlist(limits, "users")) + return (true); + + limits = nvlist_get_nvlist(limits, "users"); + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + if (uid != (uid_t)-1 && + nvlist_get_number(limits, name) == (uint64_t)uid) { + return (true); + } + break; + case NV_TYPE_STRING: + if (uname != NULL && + strcmp(nvlist_get_string(limits, name), + uname) == 0) { + return (true); + } + break; + default: + abort(); + } + } + + return (false); +} + +static int +pwd_allowed_users(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name, *uname; + void *cookie; + uid_t uid; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + switch (type) { + case NV_TYPE_NUMBER: + uid = (uid_t)nvlist_get_number(newlimits, name); + uname = NULL; + break; + case NV_TYPE_STRING: + uid = (uid_t)-1; + uname = nvlist_get_string(newlimits, name); + break; + default: + return (EINVAL); + } + if (!pwd_allowed_user(oldlimits, uname, uid)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +pwd_allowed_field(const nvlist_t *limits, const char *field) +{ + + if (limits == NULL) + return (true); + + /* + * If no limit was set on allowed fields, then all fields are allowed. + */ + if (!nvlist_exists_nvlist(limits, "fields")) + return (true); + + limits = nvlist_get_nvlist(limits, "fields"); + return (nvlist_exists_null(limits, field)); +} + +static int +pwd_allowed_fields(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const char *name; + void *cookie; + int type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NULL) + return (EINVAL); + if (!pwd_allowed_field(oldlimits, name)) + return (ENOTCAPABLE); + } + + return (0); +} + +static bool +pwd_pack(const nvlist_t *limits, const struct passwd *pwd, nvlist_t *nvl) +{ + int fields; + + if (pwd == NULL) + return (true); + + /* + * If either name or UID is allowed, we allow it. + */ + if (!pwd_allowed_user(limits, pwd->pw_name, pwd->pw_uid)) + return (false); + + fields = pwd->pw_fields; + + if (pwd_allowed_field(limits, "pw_name")) { + nvlist_add_string(nvl, "pw_name", pwd->pw_name); + } else { + nvlist_add_string(nvl, "pw_name", ""); + fields &= ~_PWF_NAME; + } + if (pwd_allowed_field(limits, "pw_uid")) { + nvlist_add_number(nvl, "pw_uid", (uint64_t)pwd->pw_uid); + } else { + nvlist_add_number(nvl, "pw_uid", (uint64_t)-1); + fields &= ~_PWF_UID; + } + if (pwd_allowed_field(limits, "pw_gid")) { + nvlist_add_number(nvl, "pw_gid", (uint64_t)pwd->pw_gid); + } else { + nvlist_add_number(nvl, "pw_gid", (uint64_t)-1); + fields &= ~_PWF_GID; + } + if (pwd_allowed_field(limits, "pw_change")) { + nvlist_add_number(nvl, "pw_change", (uint64_t)pwd->pw_change); + } else { + nvlist_add_number(nvl, "pw_change", (uint64_t)0); + fields &= ~_PWF_CHANGE; + } + if (pwd_allowed_field(limits, "pw_passwd")) { + nvlist_add_string(nvl, "pw_passwd", pwd->pw_passwd); + } else { + nvlist_add_string(nvl, "pw_passwd", ""); + fields &= ~_PWF_PASSWD; + } + if (pwd_allowed_field(limits, "pw_class")) { + nvlist_add_string(nvl, "pw_class", pwd->pw_class); + } else { + nvlist_add_string(nvl, "pw_class", ""); + fields &= ~_PWF_CLASS; + } + if (pwd_allowed_field(limits, "pw_gecos")) { + nvlist_add_string(nvl, "pw_gecos", pwd->pw_gecos); + } else { + nvlist_add_string(nvl, "pw_gecos", ""); + fields &= ~_PWF_GECOS; + } + if (pwd_allowed_field(limits, "pw_dir")) { + nvlist_add_string(nvl, "pw_dir", pwd->pw_dir); + } else { + nvlist_add_string(nvl, "pw_dir", ""); + fields &= ~_PWF_DIR; + } + if (pwd_allowed_field(limits, "pw_shell")) { + nvlist_add_string(nvl, "pw_shell", pwd->pw_shell); + } else { + nvlist_add_string(nvl, "pw_shell", ""); + fields &= ~_PWF_SHELL; + } + if (pwd_allowed_field(limits, "pw_expire")) { + nvlist_add_number(nvl, "pw_expire", (uint64_t)pwd->pw_expire); + } else { + nvlist_add_number(nvl, "pw_expire", (uint64_t)0); + fields &= ~_PWF_EXPIRE; + } + nvlist_add_number(nvl, "pw_fields", (uint64_t)fields); + + return (true); +} + +static int +pwd_getpwent(const nvlist_t *limits, const nvlist_t *nvlin __unused, + nvlist_t *nvlout) +{ + struct passwd *pwd; + + for (;;) { + errno = 0; + pwd = getpwent(); + if (errno != 0) + return (errno); + if (pwd_pack(limits, pwd, nvlout)) + return (0); + } + + /* NOTREACHED */ +} + +static int +pwd_getpwnam(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct passwd *pwd; + const char *name; + + if (!nvlist_exists_string(nvlin, "name")) + return (EINVAL); + name = nvlist_get_string(nvlin, "name"); + assert(name != NULL); + + errno = 0; + pwd = getpwnam(name); + if (errno != 0) + return (errno); + + (void)pwd_pack(limits, pwd, nvlout); + + return (0); +} + +static int +pwd_getpwuid(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + struct passwd *pwd; + uid_t uid; + + if (!nvlist_exists_number(nvlin, "uid")) + return (EINVAL); + + uid = (uid_t)nvlist_get_number(nvlin, "uid"); + + errno = 0; + pwd = getpwuid(uid); + if (errno != 0) + return (errno); + + (void)pwd_pack(limits, pwd, nvlout); + + return (0); +} + +static int +pwd_setpassent(const nvlist_t *limits __unused, const nvlist_t *nvlin, + nvlist_t *nvlout __unused) +{ + int stayopen; + + if (!nvlist_exists_bool(nvlin, "stayopen")) + return (EINVAL); + + stayopen = nvlist_get_bool(nvlin, "stayopen") ? 1 : 0; + + return (setpassent(stayopen) == 0 ? EFAULT : 0); +} + +static int +pwd_setpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused, + nvlist_t *nvlout __unused) +{ + + setpwent(); + + return (0); +} + +static int +pwd_endpwent(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused, + nvlist_t *nvlout __unused) +{ + + endpwent(); + + return (0); +} + +static int +pwd_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const nvlist_t *limits; + const char *name; + void *cookie; + int error, type; + + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "cmds") && + !nvlist_exists_nvlist(newlimits, "cmds")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "fields") && + !nvlist_exists_nvlist(newlimits, "fields")) { + return (ENOTCAPABLE); + } + if (oldlimits != NULL && nvlist_exists_nvlist(oldlimits, "users") && + !nvlist_exists_nvlist(newlimits, "users")) { + return (ENOTCAPABLE); + } + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (type != NV_TYPE_NVLIST) + return (EINVAL); + limits = nvlist_get_nvlist(newlimits, name); + if (strcmp(name, "cmds") == 0) + error = pwd_allowed_cmds(oldlimits, limits); + else if (strcmp(name, "fields") == 0) + error = pwd_allowed_fields(oldlimits, limits); + else if (strcmp(name, "users") == 0) + error = pwd_allowed_users(oldlimits, limits); + else + error = EINVAL; + if (error != 0) + return (error); + } + + return (0); +} + +static int +pwd_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int error; + + if (!pwd_allowed_cmd(limits, cmd)) + return (ENOTCAPABLE); + + if (strcmp(cmd, "getpwent") == 0 || strcmp(cmd, "getpwent_r") == 0) + error = pwd_getpwent(limits, nvlin, nvlout); + else if (strcmp(cmd, "getpwnam") == 0 || strcmp(cmd, "getpwnam_r") == 0) + error = pwd_getpwnam(limits, nvlin, nvlout); + else if (strcmp(cmd, "getpwuid") == 0 || strcmp(cmd, "getpwuid_r") == 0) + error = pwd_getpwuid(limits, nvlin, nvlout); + else if (strcmp(cmd, "setpassent") == 0) + error = pwd_setpassent(limits, nvlin, nvlout); + else if (strcmp(cmd, "setpwent") == 0) + error = pwd_setpwent(limits, nvlin, nvlout); + else if (strcmp(cmd, "endpwent") == 0) + error = pwd_endpwent(limits, nvlin, nvlout); + else + error = EINVAL; + + return (error); +} + +CREATE_SERVICE("system.pwd", pwd_limit, pwd_command, 0); diff --git a/lib/libcasper/services/cap_pwd/cap_pwd.h b/lib/libcasper/services/cap_pwd/cap_pwd.h new file mode 100644 index 000000000000..496beea26392 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/cap_pwd.h @@ -0,0 +1,159 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_PWD_H_ +#define _CAP_PWD_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/cdefs.h> + +#ifdef WITH_CASPER +__BEGIN_DECLS + +struct passwd *cap_getpwent(cap_channel_t *chan); +struct passwd *cap_getpwnam(cap_channel_t *chan, const char *login); +struct passwd *cap_getpwuid(cap_channel_t *chan, uid_t uid); + +int cap_getpwent_r(cap_channel_t *chan, struct passwd *pwd, char *buffer, + size_t bufsize, struct passwd **result); +int cap_getpwnam_r(cap_channel_t *chan, const char *name, struct passwd *pwd, + char *buffer, size_t bufsize, struct passwd **result); +int cap_getpwuid_r(cap_channel_t *chan, uid_t uid, struct passwd *pwd, + char *buffer, size_t bufsize, struct passwd **result); + +int cap_setpassent(cap_channel_t *chan, int stayopen); +void cap_setpwent(cap_channel_t *chan); +void cap_endpwent(cap_channel_t *chan); + +int cap_pwd_limit_cmds(cap_channel_t *chan, const char * const *cmds, + size_t ncmds); +int cap_pwd_limit_fields(cap_channel_t *chan, const char * const *fields, + size_t nfields); +int cap_pwd_limit_users(cap_channel_t *chan, const char * const *names, + size_t nnames, uid_t *uids, size_t nuids); + +__END_DECLS + +#else + +static inline struct passwd * +cap_getpwent(cap_channel_t *chan __unused) +{ + + return (getpwent()); +} + +static inline struct passwd * +cap_getpwnam(cap_channel_t *chan __unused, const char *login) +{ + + return (getpwnam(login)); +} + +static inline struct passwd * +cap_getpwuid(cap_channel_t *chan __unused, uid_t uid) +{ + + return (getpwuid(uid)); +} + +static inline int +cap_getpwent_r(cap_channel_t *chan __unused, struct passwd *pwd, char *buffer, + size_t bufsize, struct passwd **result) +{ + + return (getpwent_r(pwd, buffer, bufsize, result)); +} + +static inline int +cap_getpwnam_r(cap_channel_t *chan __unused, const char *name, + struct passwd *pwd, char *buffer, size_t bufsize, struct passwd **result) +{ + + return (getpwnam_r(name, pwd, buffer, bufsize, result)); +} + +static inline int +cap_getpwuid_r(cap_channel_t *chan __unused, uid_t uid, struct passwd *pwd, + char *buffer, size_t bufsize, struct passwd **result) +{ + + return (getpwuid_r(uid, pwd, buffer, bufsize, result)); +} + +static inline int +cap_setpassent(cap_channel_t *chan __unused, int stayopen) +{ + + return (setpassent(stayopen)); +} + +static inline void +cap_setpwent(cap_channel_t *chan __unused) +{ + + return (setpwent()); +} + +static inline void +cap_endpwent(cap_channel_t *chan __unused) +{ + + return (endpwent()); +} + +static inline int +cap_pwd_limit_cmds(cap_channel_t *chan __unused, + const char * const *cmds __unused, size_t ncmds __unused) +{ + + return (0); +} + +static inline int +cap_pwd_limit_fields(cap_channel_t *chan __unused, + const char * const *fields __unused, size_t nfields __unused) +{ + + return (0); +} + +static inline int +cap_pwd_limit_users(cap_channel_t *chan __unused, + const char * const *names __unused, size_t nnames __unused, + uid_t *uids __unused, size_t nuids __unused) +{ + + return (0); +} +#endif + +#endif /* !_CAP_PWD_H_ */ diff --git a/lib/libcasper/services/cap_pwd/tests/Makefile b/lib/libcasper/services/cap_pwd/tests/Makefile new file mode 100644 index 000000000000..79df81416d45 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/tests/Makefile @@ -0,0 +1,12 @@ +.include <src.opts.mk> + +TAP_TESTS_C= pwd_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_pwd +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_pwd/tests/Makefile.depend b/lib/libcasper/services/cap_pwd/tests/Makefile.depend new file mode 100644 index 000000000000..e03864c8ad46 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/tests/Makefile.depend @@ -0,0 +1,18 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcasper/services/cap_pwd \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_pwd/tests/pwd_test.c b/lib/libcasper/services/cap_pwd/tests/pwd_test.c new file mode 100644 index 000000000000..68bb00650350 --- /dev/null +++ b/lib/libcasper/services/cap_pwd/tests/pwd_test.c @@ -0,0 +1,1538 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/capsicum.h> +#include <sys/nv.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> + +#include <casper/cap_pwd.h> + +static int ntest = 1; + +#define CHECK(expr) do { \ + if ((expr)) \ + printf("ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \ + else \ + printf("not ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \ + fflush(stdout); \ + ntest++; \ +} while (0) +#define CHECKX(expr) do { \ + if ((expr)) { \ + printf("ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \ + } else { \ + printf("not ok %d # %s:%u\n", ntest, __FILE__, __LINE__); \ + exit(1); \ + } \ + fflush(stdout); \ + ntest++; \ +} while (0) + +#define UID_ROOT 0 +#define UID_OPERATOR 2 + +#define GETPWENT0 0x0001 +#define GETPWENT1 0x0002 +#define GETPWENT2 0x0004 +#define GETPWENT (GETPWENT0 | GETPWENT1 | GETPWENT2) +#define GETPWENT_R0 0x0008 +#define GETPWENT_R1 0x0010 +#define GETPWENT_R2 0x0020 +#define GETPWENT_R (GETPWENT_R0 | GETPWENT_R1 | GETPWENT_R2) +#define GETPWNAM 0x0040 +#define GETPWNAM_R 0x0080 +#define GETPWUID 0x0100 +#define GETPWUID_R 0x0200 + +static bool +passwd_compare(const struct passwd *pwd0, const struct passwd *pwd1) +{ + + if (pwd0 == NULL && pwd1 == NULL) + return (true); + if (pwd0 == NULL || pwd1 == NULL) + return (false); + + if (strcmp(pwd0->pw_name, pwd1->pw_name) != 0) + return (false); + + if (pwd0->pw_passwd != NULL || pwd1->pw_passwd != NULL) { + if (pwd0->pw_passwd == NULL || pwd1->pw_passwd == NULL) + return (false); + if (strcmp(pwd0->pw_passwd, pwd1->pw_passwd) != 0) + return (false); + } + + if (pwd0->pw_uid != pwd1->pw_uid) + return (false); + + if (pwd0->pw_gid != pwd1->pw_gid) + return (false); + + if (pwd0->pw_change != pwd1->pw_change) + return (false); + + if (pwd0->pw_class != NULL || pwd1->pw_class != NULL) { + if (pwd0->pw_class == NULL || pwd1->pw_class == NULL) + return (false); + if (strcmp(pwd0->pw_class, pwd1->pw_class) != 0) + return (false); + } + + if (pwd0->pw_gecos != NULL || pwd1->pw_gecos != NULL) { + if (pwd0->pw_gecos == NULL || pwd1->pw_gecos == NULL) + return (false); + if (strcmp(pwd0->pw_gecos, pwd1->pw_gecos) != 0) + return (false); + } + + if (pwd0->pw_dir != NULL || pwd1->pw_dir != NULL) { + if (pwd0->pw_dir == NULL || pwd1->pw_dir == NULL) + return (false); + if (strcmp(pwd0->pw_dir, pwd1->pw_dir) != 0) + return (false); + } + + if (pwd0->pw_shell != NULL || pwd1->pw_shell != NULL) { + if (pwd0->pw_shell == NULL || pwd1->pw_shell == NULL) + return (false); + if (strcmp(pwd0->pw_shell, pwd1->pw_shell) != 0) + return (false); + } + + if (pwd0->pw_expire != pwd1->pw_expire) + return (false); + + if (pwd0->pw_fields != pwd1->pw_fields) + return (false); + + return (true); +} + +static unsigned int +runtest_cmds(cap_channel_t *cappwd) +{ + char bufs[1024], bufc[1024]; + unsigned int result; + struct passwd *pwds, *pwdc; + struct passwd sts, stc; + + result = 0; + + setpwent(); + cap_setpwent(cappwd); + + pwds = getpwent(); + pwdc = cap_getpwent(cappwd); + if (passwd_compare(pwds, pwdc)) { + result |= GETPWENT0; + pwds = getpwent(); + pwdc = cap_getpwent(cappwd); + if (passwd_compare(pwds, pwdc)) + result |= GETPWENT1; + } + + getpwent_r(&sts, bufs, sizeof(bufs), &pwds); + cap_getpwent_r(cappwd, &stc, bufc, sizeof(bufc), &pwdc); + if (passwd_compare(pwds, pwdc)) { + result |= GETPWENT_R0; + getpwent_r(&sts, bufs, sizeof(bufs), &pwds); + cap_getpwent_r(cappwd, &stc, bufc, sizeof(bufc), &pwdc); + if (passwd_compare(pwds, pwdc)) + result |= GETPWENT_R1; + } + + setpwent(); + cap_setpwent(cappwd); + + getpwent_r(&sts, bufs, sizeof(bufs), &pwds); + cap_getpwent_r(cappwd, &stc, bufc, sizeof(bufc), &pwdc); + if (passwd_compare(pwds, pwdc)) + result |= GETPWENT_R2; + + pwds = getpwent(); + pwdc = cap_getpwent(cappwd); + if (passwd_compare(pwds, pwdc)) + result |= GETPWENT2; + + pwds = getpwnam("root"); + pwdc = cap_getpwnam(cappwd, "root"); + if (passwd_compare(pwds, pwdc)) { + pwds = getpwnam("operator"); + pwdc = cap_getpwnam(cappwd, "operator"); + if (passwd_compare(pwds, pwdc)) + result |= GETPWNAM; + } + + getpwnam_r("root", &sts, bufs, sizeof(bufs), &pwds); + cap_getpwnam_r(cappwd, "root", &stc, bufc, sizeof(bufc), &pwdc); + if (passwd_compare(pwds, pwdc)) { + getpwnam_r("operator", &sts, bufs, sizeof(bufs), &pwds); + cap_getpwnam_r(cappwd, "operator", &stc, bufc, sizeof(bufc), + &pwdc); + if (passwd_compare(pwds, pwdc)) + result |= GETPWNAM_R; + } + + pwds = getpwuid(UID_ROOT); + pwdc = cap_getpwuid(cappwd, UID_ROOT); + if (passwd_compare(pwds, pwdc)) { + pwds = getpwuid(UID_OPERATOR); + pwdc = cap_getpwuid(cappwd, UID_OPERATOR); + if (passwd_compare(pwds, pwdc)) + result |= GETPWUID; + } + + getpwuid_r(UID_ROOT, &sts, bufs, sizeof(bufs), &pwds); + cap_getpwuid_r(cappwd, UID_ROOT, &stc, bufc, sizeof(bufc), &pwdc); + if (passwd_compare(pwds, pwdc)) { + getpwuid_r(UID_OPERATOR, &sts, bufs, sizeof(bufs), &pwds); + cap_getpwuid_r(cappwd, UID_OPERATOR, &stc, bufc, sizeof(bufc), + &pwdc); + if (passwd_compare(pwds, pwdc)) + result |= GETPWUID_R; + } + + return (result); +} + +static void +test_cmds(cap_channel_t *origcappwd) +{ + cap_channel_t *cappwd; + const char *cmds[7], *fields[10], *names[6]; + uid_t uids[5]; + + fields[0] = "pw_name"; + fields[1] = "pw_passwd"; + fields[2] = "pw_uid"; + fields[3] = "pw_gid"; + fields[4] = "pw_change"; + fields[5] = "pw_class"; + fields[6] = "pw_gecos"; + fields[7] = "pw_dir"; + fields[8] = "pw_shell"; + fields[9] = "pw_expire"; + + names[0] = "root"; + names[1] = "toor"; + names[2] = "daemon"; + names[3] = "operator"; + names[4] = "bin"; + names[5] = "kmem"; + + uids[0] = 0; + uids[1] = 1; + uids[2] = 2; + uids[3] = 3; + uids[4] = 5; + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == 0); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == 0); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: setpwent + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cap_setpwent(cappwd); + + cmds[0] = "getpwent"; + cmds[1] = "getpwent_r"; + cmds[2] = "getpwnam"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "setpwent"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 | GETPWENT_R0 | + GETPWENT_R1 | GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: setpwent + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cap_setpwent(cappwd); + + cmds[0] = "getpwent"; + cmds[1] = "getpwent_r"; + cmds[2] = "getpwnam"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "setpwent"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 | GETPWENT_R0 | + GETPWENT_R1 | GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: getpwent + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent_r"; + cmds[2] = "getpwnam"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwent"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT_R2 | + GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getpwent + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent_r"; + cmds[2] = "getpwnam"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwent"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT_R2 | + GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: getpwent_r + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwnam"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwent_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 | + GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwnam, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getpwent_r + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwnam"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwent_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT0 | GETPWENT1 | + GETPWNAM | GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: getpwnam + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwnam"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam_r, + * getpwuid, getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getpwnam + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam_r"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwnam"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM_R | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, + * getpwuid, getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: getpwnam_r + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwnam_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, + * getpwuid, getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getpwnam_r + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwuid"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwnam_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWUID | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid_r + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: getpwuid + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwuid"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWNAM_R | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid_r + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getpwuid + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwuid"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWNAM_R | GETPWUID_R)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid + * users: + * names: root, toor, daemon, operator, bin, kmem + * uids: + * Disallow: + * cmds: getpwuid_r + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWNAM_R | GETPWUID)); + + cap_close(cappwd); + + /* + * Allow: + * cmds: setpwent, getpwent, getpwent_r, getpwnam, getpwnam_r, + * getpwuid + * users: + * names: + * uids: 0, 1, 2, 3, 5 + * Disallow: + * cmds: getpwuid_r + * users: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 6) == 0); + cmds[0] = "setpwent"; + cmds[1] = "getpwent"; + cmds[2] = "getpwent_r"; + cmds[3] = "getpwnam"; + cmds[4] = "getpwnam_r"; + cmds[5] = "getpwuid"; + cmds[6] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 7) == -1 && errno == ENOTCAPABLE); + cmds[0] = "getpwuid_r"; + CHECK(cap_pwd_limit_cmds(cappwd, cmds, 1) == -1 && errno == ENOTCAPABLE); + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 5) == 0); + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | + GETPWNAM | GETPWNAM_R | GETPWUID)); + + cap_close(cappwd); +} + +#define PW_NAME _PWF_NAME +#define PW_PASSWD _PWF_PASSWD +#define PW_UID _PWF_UID +#define PW_GID _PWF_GID +#define PW_CHANGE _PWF_CHANGE +#define PW_CLASS _PWF_CLASS +#define PW_GECOS _PWF_GECOS +#define PW_DIR _PWF_DIR +#define PW_SHELL _PWF_SHELL +#define PW_EXPIRE _PWF_EXPIRE + +static unsigned int +passwd_fields(const struct passwd *pwd) +{ + unsigned int result; + + result = 0; + + if (pwd->pw_name != NULL && pwd->pw_name[0] != '\0') + result |= PW_NAME; +// else +// printf("No pw_name\n"); + + if (pwd->pw_passwd != NULL && pwd->pw_passwd[0] != '\0') + result |= PW_PASSWD; + else if ((pwd->pw_fields & _PWF_PASSWD) != 0) + result |= PW_PASSWD; +// else +// printf("No pw_passwd\n"); + + if (pwd->pw_uid != (uid_t)-1) + result |= PW_UID; +// else +// printf("No pw_uid\n"); + + if (pwd->pw_gid != (gid_t)-1) + result |= PW_GID; +// else +// printf("No pw_gid\n"); + + if (pwd->pw_change != 0 || (pwd->pw_fields & _PWF_CHANGE) != 0) + result |= PW_CHANGE; +// else +// printf("No pw_change\n"); + + if (pwd->pw_class != NULL && pwd->pw_class[0] != '\0') + result |= PW_CLASS; + else if ((pwd->pw_fields & _PWF_CLASS) != 0) + result |= PW_CLASS; +// else +// printf("No pw_class\n"); + + if (pwd->pw_gecos != NULL && pwd->pw_gecos[0] != '\0') + result |= PW_GECOS; + else if ((pwd->pw_fields & _PWF_GECOS) != 0) + result |= PW_GECOS; +// else +// printf("No pw_gecos\n"); + + if (pwd->pw_dir != NULL && pwd->pw_dir[0] != '\0') + result |= PW_DIR; + else if ((pwd->pw_fields & _PWF_DIR) != 0) + result |= PW_DIR; +// else +// printf("No pw_dir\n"); + + if (pwd->pw_shell != NULL && pwd->pw_shell[0] != '\0') + result |= PW_SHELL; + else if ((pwd->pw_fields & _PWF_SHELL) != 0) + result |= PW_SHELL; +// else +// printf("No pw_shell\n"); + + if (pwd->pw_expire != 0 || (pwd->pw_fields & _PWF_EXPIRE) != 0) + result |= PW_EXPIRE; +// else +// printf("No pw_expire\n"); + +if (false && pwd->pw_fields != (int)result) { +printf("fields=0x%x != result=0x%x\n", (const unsigned int)pwd->pw_fields, result); +printf(" fields result\n"); +printf("PW_NAME %d %d\n", (pwd->pw_fields & PW_NAME) != 0, (result & PW_NAME) != 0); +printf("PW_PASSWD %d %d\n", (pwd->pw_fields & PW_PASSWD) != 0, (result & PW_PASSWD) != 0); +printf("PW_UID %d %d\n", (pwd->pw_fields & PW_UID) != 0, (result & PW_UID) != 0); +printf("PW_GID %d %d\n", (pwd->pw_fields & PW_GID) != 0, (result & PW_GID) != 0); +printf("PW_CHANGE %d %d\n", (pwd->pw_fields & PW_CHANGE) != 0, (result & PW_CHANGE) != 0); +printf("PW_CLASS %d %d\n", (pwd->pw_fields & PW_CLASS) != 0, (result & PW_CLASS) != 0); +printf("PW_GECOS %d %d\n", (pwd->pw_fields & PW_GECOS) != 0, (result & PW_GECOS) != 0); +printf("PW_DIR %d %d\n", (pwd->pw_fields & PW_DIR) != 0, (result & PW_DIR) != 0); +printf("PW_SHELL %d %d\n", (pwd->pw_fields & PW_SHELL) != 0, (result & PW_SHELL) != 0); +printf("PW_EXPIRE %d %d\n", (pwd->pw_fields & PW_EXPIRE) != 0, (result & PW_EXPIRE) != 0); +} + +//printf("result=0x%x\n", result); + return (result); +} + +static bool +runtest_fields(cap_channel_t *cappwd, unsigned int expected) +{ + char buf[1024]; + struct passwd *pwd; + struct passwd st; + +//printf("expected=0x%x\n", expected); + cap_setpwent(cappwd); + pwd = cap_getpwent(cappwd); + if ((passwd_fields(pwd) & ~expected) != 0) + return (false); + + cap_setpwent(cappwd); + cap_getpwent_r(cappwd, &st, buf, sizeof(buf), &pwd); + if ((passwd_fields(pwd) & ~expected) != 0) + return (false); + + pwd = cap_getpwnam(cappwd, "root"); + if ((passwd_fields(pwd) & ~expected) != 0) + return (false); + + cap_getpwnam_r(cappwd, "root", &st, buf, sizeof(buf), &pwd); + if ((passwd_fields(pwd) & ~expected) != 0) + return (false); + + pwd = cap_getpwuid(cappwd, UID_ROOT); + if ((passwd_fields(pwd) & ~expected) != 0) + return (false); + + cap_getpwuid_r(cappwd, UID_ROOT, &st, buf, sizeof(buf), &pwd); + if ((passwd_fields(pwd) & ~expected) != 0) + return (false); + + return (true); +} + +static void +test_fields(cap_channel_t *origcappwd) +{ + cap_channel_t *cappwd; + const char *fields[10]; + + /* No limits. */ + + CHECK(runtest_fields(origcappwd, PW_NAME | PW_PASSWD | PW_UID | + PW_GID | PW_CHANGE | PW_CLASS | PW_GECOS | PW_DIR | PW_SHELL | + PW_EXPIRE)); + + /* + * Allow: + * fields: pw_name, pw_passwd, pw_uid, pw_gid, pw_change, pw_class, + * pw_gecos, pw_dir, pw_shell, pw_expire + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_name"; + fields[1] = "pw_passwd"; + fields[2] = "pw_uid"; + fields[3] = "pw_gid"; + fields[4] = "pw_change"; + fields[5] = "pw_class"; + fields[6] = "pw_gecos"; + fields[7] = "pw_dir"; + fields[8] = "pw_shell"; + fields[9] = "pw_expire"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 10) == 0); + + CHECK(runtest_fields(origcappwd, PW_NAME | PW_PASSWD | PW_UID | + PW_GID | PW_CHANGE | PW_CLASS | PW_GECOS | PW_DIR | PW_SHELL | + PW_EXPIRE)); + + cap_close(cappwd); + + /* + * Allow: + * fields: pw_name, pw_passwd, pw_uid, pw_gid, pw_change + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_name"; + fields[1] = "pw_passwd"; + fields[2] = "pw_uid"; + fields[3] = "pw_gid"; + fields[4] = "pw_change"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0); + fields[5] = "pw_class"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 && + errno == ENOTCAPABLE); + fields[0] = "pw_class"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(cappwd, PW_NAME | PW_PASSWD | PW_UID | + PW_GID | PW_CHANGE)); + + cap_close(cappwd); + + /* + * Allow: + * fields: pw_class, pw_gecos, pw_dir, pw_shell, pw_expire + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_class"; + fields[1] = "pw_gecos"; + fields[2] = "pw_dir"; + fields[3] = "pw_shell"; + fields[4] = "pw_expire"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0); + fields[5] = "pw_uid"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 && + errno == ENOTCAPABLE); + fields[0] = "pw_uid"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(cappwd, PW_CLASS | PW_GECOS | PW_DIR | + PW_SHELL | PW_EXPIRE)); + + cap_close(cappwd); + + /* + * Allow: + * fields: pw_name, pw_uid, pw_change, pw_gecos, pw_shell + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_name"; + fields[1] = "pw_uid"; + fields[2] = "pw_change"; + fields[3] = "pw_gecos"; + fields[4] = "pw_shell"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0); + fields[5] = "pw_class"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 && + errno == ENOTCAPABLE); + fields[0] = "pw_class"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(cappwd, PW_NAME | PW_UID | PW_CHANGE | + PW_GECOS | PW_SHELL)); + + cap_close(cappwd); + + /* + * Allow: + * fields: pw_passwd, pw_gid, pw_class, pw_dir, pw_expire + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_passwd"; + fields[1] = "pw_gid"; + fields[2] = "pw_class"; + fields[3] = "pw_dir"; + fields[4] = "pw_expire"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 5) == 0); + fields[5] = "pw_uid"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 6) == -1 && + errno == ENOTCAPABLE); + fields[0] = "pw_uid"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(cappwd, PW_PASSWD | PW_GID | PW_CLASS | + PW_DIR | PW_EXPIRE)); + + cap_close(cappwd); + + /* + * Allow: + * fields: pw_uid, pw_class, pw_shell + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_uid"; + fields[1] = "pw_class"; + fields[2] = "pw_shell"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 3) == 0); + fields[3] = "pw_change"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 4) == -1 && + errno == ENOTCAPABLE); + fields[0] = "pw_change"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(cappwd, PW_UID | PW_CLASS | PW_SHELL)); + + cap_close(cappwd); + + /* + * Allow: + * fields: pw_change + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + fields[0] = "pw_change"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == 0); + fields[1] = "pw_uid"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 2) == -1 && + errno == ENOTCAPABLE); + fields[0] = "pw_uid"; + CHECK(cap_pwd_limit_fields(cappwd, fields, 1) == -1 && + errno == ENOTCAPABLE); + + CHECK(runtest_fields(cappwd, PW_CHANGE)); + + cap_close(cappwd); +} + +static bool +runtest_users(cap_channel_t *cappwd, const char **names, const uid_t *uids, + size_t nusers) +{ + char buf[1024]; + struct passwd *pwd; + struct passwd st; + unsigned int i, got; + + cap_setpwent(cappwd); + got = 0; + for (;;) { + pwd = cap_getpwent(cappwd); + if (pwd == NULL) + break; + got++; + for (i = 0; i < nusers; i++) { + if (strcmp(names[i], pwd->pw_name) == 0 && + uids[i] == pwd->pw_uid) { + break; + } + } + if (i == nusers) + return (false); + } + if (got != nusers) + return (false); + + cap_setpwent(cappwd); + got = 0; + for (;;) { + cap_getpwent_r(cappwd, &st, buf, sizeof(buf), &pwd); + if (pwd == NULL) + break; + got++; + for (i = 0; i < nusers; i++) { + if (strcmp(names[i], pwd->pw_name) == 0 && + uids[i] == pwd->pw_uid) { + break; + } + } + if (i == nusers) + return (false); + } + if (got != nusers) + return (false); + + for (i = 0; i < nusers; i++) { + pwd = cap_getpwnam(cappwd, names[i]); + if (pwd == NULL) + return (false); + } + + for (i = 0; i < nusers; i++) { + cap_getpwnam_r(cappwd, names[i], &st, buf, sizeof(buf), &pwd); + if (pwd == NULL) + return (false); + } + + for (i = 0; i < nusers; i++) { + pwd = cap_getpwuid(cappwd, uids[i]); + if (pwd == NULL) + return (false); + } + + for (i = 0; i < nusers; i++) { + cap_getpwuid_r(cappwd, uids[i], &st, buf, sizeof(buf), &pwd); + if (pwd == NULL) + return (false); + } + + return (true); +} + +static void +test_users(cap_channel_t *origcappwd) +{ + cap_channel_t *cappwd; + const char *names[6]; + uid_t uids[6]; + + /* + * Allow: + * users: + * names: root, toor, daemon, operator, bin, tty + * uids: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "root"; + names[1] = "toor"; + names[2] = "daemon"; + names[3] = "operator"; + names[4] = "bin"; + names[5] = "tty"; + CHECK(cap_pwd_limit_users(cappwd, names, 6, NULL, 0) == 0); + uids[0] = 0; + uids[1] = 0; + uids[2] = 1; + uids[3] = 2; + uids[4] = 3; + uids[5] = 4; + + CHECK(runtest_users(cappwd, names, uids, 6)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: daemon, operator, bin + * uids: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "daemon"; + names[1] = "operator"; + names[2] = "bin"; + CHECK(cap_pwd_limit_users(cappwd, names, 3, NULL, 0) == 0); + names[3] = "tty"; + CHECK(cap_pwd_limit_users(cappwd, names, 4, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "tty"; + CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + uids[0] = 1; + uids[1] = 2; + uids[2] = 3; + + CHECK(runtest_users(cappwd, names, uids, 3)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: daemon, bin, tty + * uids: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "daemon"; + names[1] = "bin"; + names[2] = "tty"; + CHECK(cap_pwd_limit_users(cappwd, names, 3, NULL, 0) == 0); + names[3] = "operator"; + CHECK(cap_pwd_limit_users(cappwd, names, 4, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "operator"; + CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + uids[0] = 1; + uids[1] = 3; + uids[2] = 4; + + CHECK(runtest_users(cappwd, names, uids, 3)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: + * uids: 1, 2, 3 + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "daemon"; + names[1] = "operator"; + names[2] = "bin"; + uids[0] = 1; + uids[1] = 2; + uids[2] = 3; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 3) == 0); + uids[3] = 4; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 4) == -1 && + errno == ENOTCAPABLE); + uids[0] = 4; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 && + errno == ENOTCAPABLE); + uids[0] = 1; + + CHECK(runtest_users(cappwd, names, uids, 3)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: + * uids: 1, 3, 4 + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "daemon"; + names[1] = "bin"; + names[2] = "tty"; + uids[0] = 1; + uids[1] = 3; + uids[2] = 4; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 3) == 0); + uids[3] = 5; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 4) == -1 && + errno == ENOTCAPABLE); + uids[0] = 5; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 && + errno == ENOTCAPABLE); + uids[0] = 1; + + CHECK(runtest_users(cappwd, names, uids, 3)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: bin + * uids: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "bin"; + CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == 0); + names[1] = "operator"; + CHECK(cap_pwd_limit_users(cappwd, names, 2, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "operator"; + CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "bin"; + uids[0] = 3; + + CHECK(runtest_users(cappwd, names, uids, 1)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: daemon, tty + * uids: + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "daemon"; + names[1] = "tty"; + CHECK(cap_pwd_limit_users(cappwd, names, 2, NULL, 0) == 0); + names[2] = "operator"; + CHECK(cap_pwd_limit_users(cappwd, names, 3, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "operator"; + CHECK(cap_pwd_limit_users(cappwd, names, 1, NULL, 0) == -1 && + errno == ENOTCAPABLE); + names[0] = "daemon"; + uids[0] = 1; + uids[1] = 4; + + CHECK(runtest_users(cappwd, names, uids, 2)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: + * uids: 3 + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "bin"; + uids[0] = 3; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == 0); + uids[1] = 4; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 2) == -1 && + errno == ENOTCAPABLE); + uids[0] = 4; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 && + errno == ENOTCAPABLE); + uids[0] = 3; + + CHECK(runtest_users(cappwd, names, uids, 1)); + + cap_close(cappwd); + + /* + * Allow: + * users: + * names: + * uids: 1, 4 + */ + cappwd = cap_clone(origcappwd); + CHECK(cappwd != NULL); + + names[0] = "daemon"; + names[1] = "tty"; + uids[0] = 1; + uids[1] = 4; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 2) == 0); + uids[2] = 3; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 3) == -1 && + errno == ENOTCAPABLE); + uids[0] = 3; + CHECK(cap_pwd_limit_users(cappwd, NULL, 0, uids, 1) == -1 && + errno == ENOTCAPABLE); + uids[0] = 1; + + CHECK(runtest_users(cappwd, names, uids, 2)); + + cap_close(cappwd); +} + +int +main(void) +{ + cap_channel_t *capcas, *cappwd; + + printf("1..188\n"); + fflush(stdout); + + capcas = cap_init(); + CHECKX(capcas != NULL); + + cappwd = cap_service_open(capcas, "system.pwd"); + CHECKX(cappwd != NULL); + + cap_close(capcas); + + /* No limits. */ + + CHECK(runtest_cmds(cappwd) == (GETPWENT | GETPWENT_R | GETPWNAM | + GETPWNAM_R | GETPWUID | GETPWUID_R)); + + test_cmds(cappwd); + + test_fields(cappwd); + + test_users(cappwd); + + cap_close(cappwd); + + exit(0); +} diff --git a/lib/libcasper/services/cap_sysctl/Makefile b/lib/libcasper/services/cap_sysctl/Makefile new file mode 100644 index 000000000000..522313df4ffc --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/Makefile @@ -0,0 +1,34 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 2 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_sysctl + +SRCS= cap_sysctl.c +.endif + +INCS= cap_sysctl.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +MAN+= cap_sysctl.3 +MLINKS+=cap_sysctl.3 libcap_sysctl.3 \ + cap_sysctl.3 cap_sysctlbyname.3 \ + cap_sysctl.3 cap_nametomib.3 \ + cap_sysctl.3 cap_sysctl_limit_init.3 \ + cap_sysctl.3 cap_sysctl_limit_mib.3 \ + cap_sysctl.3 cap_sysctl_limit_name.3 \ + cap_sysctl.3 cap_sysctl_limit.3 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_sysctl/Makefile.depend b/lib/libcasper/services/cap_sysctl/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_sysctl/cap_sysctl.3 b/lib/libcasper/services/cap_sysctl/cap_sysctl.3 new file mode 100644 index 000000000000..2c7a491a1f8b --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.3 @@ -0,0 +1,227 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt CAP_SYSCTL 3 +.Os +.Sh NAME +.Nm cap_sysctl +.Nd "library for getting or setting system information in capability mode" +.Sh LIBRARY +.Lb libcap_sysctl +.Sh SYNOPSIS +.In libcasper.h +.In casper/cap_sysctl.h +.Ft int +.Fn cap_sysctl "cap_channel_t *chan" "const int *name" "u_int namelen" "void *oldp" "size_t *oldlenp" "const void *newp" "size_t newlen" +.Ft int +.Fn cap_sysctlbyname "cap_channel_t *chan" "const char *name" "void *oldp" "size_t *oldlenp" "const void *newp" "size_t newlen" +.Ft int +.Fn cap_sysctlnametomib "cap_channel_t *chan" "const char *name" "int *mibp" "size_t *sizep" +.Ft cap_sysctl_limit_t * +.Fn cap_sysctl_limit_init "cap_channel_t *chan" +.Ft cap_sysctl_limit_t * +.Fn cap_sysctl_limit_name "cap_sysctl_limit_t *limit" "const char *name" "int flags" +.Ft cap_sysctl_limit_t * +.Fn cap_sysctl_limit_mib "cap_sysctl_limit_t *limit" "const int *mibp" "u_int miblen" "int flags" +.Ft int +.Fn cap_sysctl_limit "cap_sysctl_limit_t *limit" +.Sh DESCRIPTION +The +.Fn cap_sysctl , +.Fn cap_sysctlbyname +and +.Fn cap_sysctlnametomib +functions are equivalent to +.Xr sysctl 3 , +.Xr sysctlbyname 3 +and +.Xr sysctlnametomib 3 , +except that they are implemented by the +.Ql system.sysctl +.Xr libcasper 3 +service and require a corresponding +.Xr libcasper 3 +capability. +.Pp +All of these functions, with the exceptions of +.Fn cap_sysctl_limit_init +and +.Fn cap_sysctl_limit_mib , +are reentrant but not thread-safe. +That is, they may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh LIMITS +By default, the +.Nm +capability provides unrestricted access to the sysctl namespace. +Applications typically only require access to a small number of sysctl +variables; the +.Fn cap_sysctl_limit +interface can be used to restrict the sysctls that can be accessed using +the +.Nm +capability. +.Fn cap_sysctl_limit_init +returns an opaque limit handle used to store a list of permitted sysctls +and access rights. +Rights are encoded using the following flags: +.Pp +.Bd -literal -offset indent -compact +CAP_SYSCTL_READ allow reads of the sysctl variable +CAP_SYSCTL_WRITE allow writes of the sysctl variable +CAP_SYSCTL_RDWR allow reads and writes of the sysctl variable +CAP_RECURSIVE permit access to any child of the sysctl variable +.Ed +.Pp +The +.Fn cap_sysctl_limit_name +function adds the sysctl identified by +.Ar name +to the limit list, and +.Fn cap_sysctl_limit_mib +function adds the sysctl identified by +.Ar mibp +to the limit list. +The access rights for the sysctl are specified in the +.Ar flags +parameter; at least one of +.Dv CAP_SYSCTL_READ , +.Dv CAP_SYSCTL_WRITE +and +.Dv CAP_SYSCTL_RDWR +must be specified. +.Fn cap_sysctl_limit +applies a set of sysctl limits to the capability, denying access to sysctl +variables not belonging to the set. +It consumes the limit handle. +After either success or failure, the user must not access the handle again. +.Pp +Once a set of limits is applied, subsequent calls to +.Fn cap_sysctl_limit +will fail unless the new set is a subset of the current set. +.Pp +.Fn cap_sysctlnametomib +will succeed so long as the named sysctl variable is present in the limit set, +regardless of its access rights. +When a sysctl variable name is added to a limit set, its MIB identifier is +automatically added to the set. +.Sh EXAMPLES +The following example first opens a capability to casper, uses this +capability to create the +.Nm system.sysctl +casper service, and then uses the +.Nm +capability to get the value of +.Dv kern.trap_enotcap . +.Bd -literal +cap_channel_t *capcas, *capsysctl; +const char *name = "kern.trap_enotcap"; +void *limit; +size_t size; +bool value; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Enter capability mode sandbox. */ +if (cap_enter() < 0 && errno != ENOSYS) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.sysctl service. */ +capsysctl = cap_service_open(capcas, "system.sysctl"); +if (capsysctl == NULL) + err(1, "Unable to open system.sysctl service"); + +/* Close Casper capability, we don't need it anymore. */ +cap_close(capcas); + +/* Create limit for one MIB with read access only. */ +limit = cap_sysctl_limit_init(capsysctl); +(void)cap_sysctl_limit_name(limit, name, CAP_SYSCTL_READ); + +/* Limit system.sysctl. */ +if (cap_sysctl_limit(limit) < 0) + err(1, "Unable to set limits"); + +/* Fetch value. */ +size = sizeof(value); +if (cap_sysctlbyname(capsysctl, name, &value, &size, NULL, 0) < 0) + err(1, "Unable to get value of sysctl"); + +printf("The value of %s is %d.\\n", name, value); + +cap_close(capsysctl); +.Ed +.Sh RETURN VALUES +.Fn cap_sysctl_limit_init +will return a new limit handle on success or +.Dv NULL +on failure, and set +.Va errno . +.Fn cap_sysctl_limit_mib +and +.Fn cap_sysctl_limit_name +will return the modified limit handle on success or +.Dv NULL +on failure and set +.Va errno . +After failure, the caller must not access the limit handle again. +.Fn cap_sysctl_limit +will return +.Dv -1 +on failure and set +.Va errno . +.Fn cap_sysctl , +.Fn cap_sysctlbyname , +and +.Fn cap_sysctlnametomib +have the same return values as their non-capability-mode equivalents as +documented in +.Xr sysctl 3 . +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr err 3 , +.Xr sysctl 3 , +.Xr sysctlbyname 3 , +.Xr sysctlnametomib 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm cap_sysctl +service first appeared in +.Fx 10.3 . +.Sh AUTHORS +The +.Nm cap_sysctl +service was implemented by +.An Pawel Jakub Dawidek Aq Mt pawel@dawidek.net +under sponsorship from the FreeBSD Foundation. +.Pp +This manual page was written by +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org . diff --git a/lib/libcasper/services/cap_sysctl/cap_sysctl.c b/lib/libcasper/services/cap_sysctl/cap_sysctl.c new file mode 100644 index 000000000000..c99fd74cb169 --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.c @@ -0,0 +1,531 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013, 2018 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Portions of this software were developed by Mark Johnston + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> +#include <sys/cnv.h> +#include <sys/dnv.h> +#include <sys/nv.h> +#include <sys/sysctl.h> + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_sysctl.h" + +/* + * Limit interface. + */ + +struct cap_sysctl_limit { + cap_channel_t *chan; + nvlist_t *nv; +}; + +cap_sysctl_limit_t * +cap_sysctl_limit_init(cap_channel_t *chan) +{ + cap_sysctl_limit_t *limit; + int error; + + limit = malloc(sizeof(*limit)); + if (limit != NULL) { + limit->chan = chan; + limit->nv = nvlist_create(NV_FLAG_NO_UNIQUE); + if (limit->nv == NULL) { + error = errno; + free(limit); + limit = NULL; + errno = error; + } + } + return (limit); +} + +cap_sysctl_limit_t * +cap_sysctl_limit_name(cap_sysctl_limit_t *limit, const char *name, int flags) +{ + nvlist_t *lnv; + size_t mibsz; + int error, mib[CTL_MAXNAME]; + + lnv = nvlist_create(0); + if (lnv == NULL) { + error = errno; + if (limit->nv != NULL) + nvlist_destroy(limit->nv); + free(limit); + errno = error; + return (NULL); + } + nvlist_add_string(lnv, "name", name); + nvlist_add_number(lnv, "operation", flags); + + mibsz = nitems(mib); + error = cap_sysctlnametomib(limit->chan, name, mib, &mibsz); + if (error == 0) + nvlist_add_binary(lnv, "mib", mib, mibsz * sizeof(int)); + + nvlist_move_nvlist(limit->nv, "limit", lnv); + return (limit); +} + +cap_sysctl_limit_t * +cap_sysctl_limit_mib(cap_sysctl_limit_t *limit, const int *mibp, u_int miblen, + int flags) +{ + nvlist_t *lnv; + int error; + + lnv = nvlist_create(0); + if (lnv == NULL) { + error = errno; + if (limit->nv != NULL) + nvlist_destroy(limit->nv); + free(limit); + errno = error; + return (NULL); + } + nvlist_add_binary(lnv, "mib", mibp, miblen * sizeof(int)); + nvlist_add_number(lnv, "operation", flags); + nvlist_add_nvlist(limit->nv, "limit", lnv); + return (limit); +} + +int +cap_sysctl_limit(cap_sysctl_limit_t *limit) +{ + cap_channel_t *chan; + nvlist_t *lnv; + + chan = limit->chan; + lnv = limit->nv; + free(limit); + + /* cap_limit_set(3) will always free the nvlist. */ + return (cap_limit_set(chan, lnv)); +} + +/* + * Service interface. + */ + +static int +do_sysctl(cap_channel_t *chan, nvlist_t *nvl, void *oldp, size_t *oldlenp, + const void *newp, size_t newlen) +{ + const uint8_t *retoldp; + size_t oldlen; + int error; + uint8_t operation; + + operation = 0; + if (oldlenp != NULL) + operation |= CAP_SYSCTL_READ; + if (newp != NULL) + operation |= CAP_SYSCTL_WRITE; + nvlist_add_number(nvl, "operation", (uint64_t)operation); + if (oldp == NULL && oldlenp != NULL) + nvlist_add_null(nvl, "justsize"); + else if (oldlenp != NULL) + nvlist_add_number(nvl, "oldlen", (uint64_t)*oldlenp); + if (newp != NULL) + nvlist_add_binary(nvl, "newp", newp, newlen); + + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) + return (-1); + error = (int)dnvlist_get_number(nvl, "error", 0); + if (error != 0) { + nvlist_destroy(nvl); + errno = error; + return (-1); + } + + if (oldp == NULL && oldlenp != NULL) { + *oldlenp = (size_t)nvlist_get_number(nvl, "oldlen"); + } else if (oldp != NULL) { + retoldp = nvlist_get_binary(nvl, "oldp", &oldlen); + memcpy(oldp, retoldp, oldlen); + if (oldlenp != NULL) + *oldlenp = oldlen; + } + + nvlist_destroy(nvl); + + return (0); +} + +int +cap_sysctl(cap_channel_t *chan, const int *name, u_int namelen, void *oldp, + size_t *oldlenp, const void *newp, size_t newlen) +{ + nvlist_t *req; + + req = nvlist_create(0); + nvlist_add_string(req, "cmd", "sysctl"); + nvlist_add_binary(req, "mib", name, (size_t)namelen * sizeof(int)); + return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen)); +} + +int +cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp, + size_t *oldlenp, const void *newp, size_t newlen) +{ + nvlist_t *req; + + req = nvlist_create(0); + nvlist_add_string(req, "cmd", "sysctlbyname"); + nvlist_add_string(req, "name", name); + return (do_sysctl(chan, req, oldp, oldlenp, newp, newlen)); +} + +int +cap_sysctlnametomib(cap_channel_t *chan, const char *name, int *mibp, + size_t *sizep) +{ + nvlist_t *req; + const void *mib; + size_t mibsz; + int error; + + req = nvlist_create(0); + nvlist_add_string(req, "cmd", "sysctlnametomib"); + nvlist_add_string(req, "name", name); + nvlist_add_number(req, "operation", 0); + nvlist_add_number(req, "size", (uint64_t)*sizep); + + req = cap_xfer_nvlist(chan, req); + if (req == NULL) + return (-1); + error = (int)dnvlist_get_number(req, "error", 0); + if (error != 0) { + nvlist_destroy(req); + errno = error; + return (-1); + } + + mib = nvlist_get_binary(req, "mib", &mibsz); + *sizep = mibsz / sizeof(int); + + memcpy(mibp, mib, mibsz); + + nvlist_destroy(req); + + return (0); +} + +/* + * Service implementation. + */ + +/* + * Validate a sysctl description. This must consist of an nvlist with either a + * binary "mib" field or a string "name", and an operation. + */ +static int +sysctl_valid(const nvlist_t *nvl, bool limit) +{ + const char *name; + void *cookie; + int type; + size_t size; + unsigned int field, fields; + + /* NULL nvl is of course invalid. */ + if (nvl == NULL) + return (EINVAL); + if (nvlist_error(nvl) != 0) + return (nvlist_error(nvl)); + +#define HAS_NAME 0x01 +#define HAS_MIB 0x02 +#define HAS_ID (HAS_NAME | HAS_MIB) +#define HAS_OPERATION 0x04 + + fields = 0; + cookie = NULL; + while ((name = nvlist_next(nvl, &type, &cookie)) != NULL) { + if ((strcmp(name, "name") == 0 && type == NV_TYPE_STRING) || + (strcmp(name, "mib") == 0 && type == NV_TYPE_BINARY)) { + if (strcmp(name, "mib") == 0) { + /* A MIB must be an array of integers. */ + (void)cnvlist_get_binary(cookie, &size); + if (size % sizeof(int) != 0) + return (EINVAL); + field = HAS_MIB; + } else + field = HAS_NAME; + + /* + * A limit may contain both a name and a MIB identifier. + */ + if ((fields & field) != 0 || + (!limit && (fields & HAS_ID) != 0)) + return (EINVAL); + fields |= field; + } else if (strcmp(name, "operation") == 0) { + uint64_t mask, operation; + + if (type != NV_TYPE_NUMBER) + return (EINVAL); + + operation = cnvlist_get_number(cookie); + + /* + * Requests can only include the RDWR flags; limits may + * also include the RECURSIVE flag. + */ + mask = limit ? (CAP_SYSCTL_RDWR | + CAP_SYSCTL_RECURSIVE) : CAP_SYSCTL_RDWR; + if ((operation & ~mask) != 0 || + (operation & CAP_SYSCTL_RDWR) == 0) + return (EINVAL); + /* Only one 'operation' can be present. */ + if ((fields & HAS_OPERATION) != 0) + return (EINVAL); + fields |= HAS_OPERATION; + } else if (limit) + return (EINVAL); + } + + if ((fields & HAS_OPERATION) == 0 || (fields & HAS_ID) == 0) + return (EINVAL); + +#undef HAS_OPERATION +#undef HAS_ID +#undef HAS_MIB +#undef HAS_NAME + + return (0); +} + +static bool +sysctl_allowed(const nvlist_t *limits, const nvlist_t *req) +{ + const nvlist_t *limit; + uint64_t op, reqop; + const char *lname, *name, *reqname; + void *cookie; + size_t lsize, reqsize; + const int *lmib, *reqmib; + int type; + + if (limits == NULL) + return (true); + + reqmib = dnvlist_get_binary(req, "mib", &reqsize, NULL, 0); + reqname = dnvlist_get_string(req, "name", NULL); + reqop = nvlist_get_number(req, "operation"); + + cookie = NULL; + while ((name = nvlist_next(limits, &type, &cookie)) != NULL) { + assert(type == NV_TYPE_NVLIST); + + limit = cnvlist_get_nvlist(cookie); + op = nvlist_get_number(limit, "operation"); + if ((reqop & op) != reqop) + continue; + + if (reqname != NULL) { + lname = dnvlist_get_string(limit, "name", NULL); + if (lname == NULL) + continue; + if ((op & CAP_SYSCTL_RECURSIVE) == 0) { + if (strcmp(lname, reqname) != 0) + continue; + } else { + size_t namelen; + + namelen = strlen(lname); + if (strncmp(lname, reqname, namelen) != 0) + continue; + if (reqname[namelen] != '.' && + reqname[namelen] != '\0') + continue; + } + } else { + lmib = dnvlist_get_binary(limit, "mib", &lsize, NULL, 0); + if (lmib == NULL) + continue; + if (lsize > reqsize || ((op & CAP_SYSCTL_RECURSIVE) == 0 && + lsize < reqsize)) + continue; + if (memcmp(lmib, reqmib, lsize) != 0) + continue; + } + + return (true); + } + + return (false); +} + +static int +sysctl_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) +{ + const nvlist_t *nvl; + const char *name; + void *cookie; + int error, type; + + cookie = NULL; + while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { + if (strcmp(name, "limit") != 0 || type != NV_TYPE_NVLIST) + return (EINVAL); + nvl = cnvlist_get_nvlist(cookie); + error = sysctl_valid(nvl, true); + if (error != 0) + return (error); + if (!sysctl_allowed(oldlimits, nvl)) + return (ENOTCAPABLE); + } + + return (0); +} + +static int +nametomib(const nvlist_t *limits, const nvlist_t *nvlin, nvlist_t *nvlout) +{ + const char *name; + size_t size; + int error, *mibp; + + if (!sysctl_allowed(limits, nvlin)) + return (ENOTCAPABLE); + + name = nvlist_get_string(nvlin, "name"); + size = (size_t)nvlist_get_number(nvlin, "size"); + + mibp = malloc(size * sizeof(*mibp)); + if (mibp == NULL) + return (ENOMEM); + + error = sysctlnametomib(name, mibp, &size); + if (error != 0) { + error = errno; + free(mibp); + return (error); + } + + nvlist_add_binary(nvlout, "mib", mibp, size * sizeof(*mibp)); + + return (0); +} + +static int +sysctl_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + const char *name; + const void *newp; + const int *mibp; + void *oldp; + uint64_t operation; + size_t oldlen, newlen, size; + size_t *oldlenp; + int error; + + if (strcmp(cmd, "sysctlnametomib") == 0) + return (nametomib(limits, nvlin, nvlout)); + + if (strcmp(cmd, "sysctlbyname") != 0 && strcmp(cmd, "sysctl") != 0) + return (EINVAL); + error = sysctl_valid(nvlin, false); + if (error != 0) + return (error); + if (!sysctl_allowed(limits, nvlin)) + return (ENOTCAPABLE); + + operation = nvlist_get_number(nvlin, "operation"); + if ((operation & CAP_SYSCTL_WRITE) != 0) { + if (!nvlist_exists_binary(nvlin, "newp")) + return (EINVAL); + newp = nvlist_get_binary(nvlin, "newp", &newlen); + assert(newp != NULL && newlen > 0); + } else { + newp = NULL; + newlen = 0; + } + + if ((operation & CAP_SYSCTL_READ) != 0) { + if (nvlist_exists_null(nvlin, "justsize")) { + oldp = NULL; + oldlen = 0; + oldlenp = &oldlen; + } else { + if (!nvlist_exists_number(nvlin, "oldlen")) + return (EINVAL); + oldlen = (size_t)nvlist_get_number(nvlin, "oldlen"); + if (oldlen == 0) + return (EINVAL); + oldp = calloc(1, oldlen); + if (oldp == NULL) + return (ENOMEM); + oldlenp = &oldlen; + } + } else { + oldp = NULL; + oldlen = 0; + oldlenp = NULL; + } + + if (strcmp(cmd, "sysctlbyname") == 0) { + name = nvlist_get_string(nvlin, "name"); + error = sysctlbyname(name, oldp, oldlenp, newp, newlen); + } else { + mibp = nvlist_get_binary(nvlin, "mib", &size); + error = sysctl(mibp, size / sizeof(*mibp), oldp, oldlenp, newp, + newlen); + } + if (error != 0) { + error = errno; + free(oldp); + return (error); + } + + if ((operation & CAP_SYSCTL_READ) != 0) { + if (nvlist_exists_null(nvlin, "justsize")) + nvlist_add_number(nvlout, "oldlen", (uint64_t)oldlen); + else + nvlist_move_binary(nvlout, "oldp", oldp, oldlen); + } + + return (0); +} + +CREATE_SERVICE("system.sysctl", sysctl_limit, sysctl_command, 0); diff --git a/lib/libcasper/services/cap_sysctl/cap_sysctl.h b/lib/libcasper/services/cap_sysctl/cap_sysctl.h new file mode 100644 index 000000000000..51243128a683 --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/cap_sysctl.h @@ -0,0 +1,123 @@ +/*- + * Copyright (c) 2013 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_SYSCTL_H_ +#define _CAP_SYSCTL_H_ + +#ifdef HAVE_CASPER +#define WITH_CASPER +#endif + +#include <sys/cdefs.h> + +#define CAP_SYSCTL_READ 0x01 +#define CAP_SYSCTL_WRITE 0x02 +#define CAP_SYSCTL_RDWR (CAP_SYSCTL_READ | CAP_SYSCTL_WRITE) +#define CAP_SYSCTL_RECURSIVE 0x04 + +struct cap_sysctl_limit; +typedef struct cap_sysctl_limit cap_sysctl_limit_t; + +#ifdef WITH_CASPER + +__BEGIN_DECLS + +int cap_sysctl(cap_channel_t *chan, const int *name, u_int namelen, void *oldp, + size_t *oldlenp, const void *newp, size_t newlen); +int cap_sysctlbyname(cap_channel_t *chan, const char *name, void *oldp, + size_t *oldlenp, const void *newp, size_t newlen); +int cap_sysctlnametomib(cap_channel_t *chan, const char *name, int *mibp, + size_t *sizep); + +cap_sysctl_limit_t *cap_sysctl_limit_init(cap_channel_t *); +cap_sysctl_limit_t *cap_sysctl_limit_name(cap_sysctl_limit_t *limit, + const char *name, int flags); +cap_sysctl_limit_t *cap_sysctl_limit_mib(cap_sysctl_limit_t *limit, + const int *mibp, u_int miblen, int flags); +int cap_sysctl_limit(cap_sysctl_limit_t *limit); + +__END_DECLS + +#else /* !WITH_CASPER */ +static inline int +cap_sysctl(cap_channel_t *chan __unused, const int *name, u_int namelen, + void *oldp, size_t *oldlenp, const void *newp, size_t newlen) +{ + + return (sysctl(name, namelen, oldp, oldlenp, newp, newlen)); +} + +static inline int +cap_sysctlbyname(cap_channel_t *chan __unused, const char *name, + void *oldp, size_t *oldlenp, const void *newp, size_t newlen) +{ + + return (sysctlbyname(name, oldp, oldlenp, newp, newlen)); +} + +static inline int +cap_sysctlnametomib(cap_channel_t *chan __unused, const char *name, int *mibp, + size_t *sizep) +{ + + return (sysctlnametomib(name, mibp, sizep)); +} + +static inline cap_sysctl_limit_t * +cap_sysctl_limit_init(cap_channel_t *limit __unused) +{ + + return (NULL); +} + +static inline cap_sysctl_limit_t * +cap_sysctl_limit_name(cap_sysctl_limit_t *limit __unused, + const char *name __unused, int flags __unused) +{ + + return (NULL); +} + +static inline cap_sysctl_limit_t * +cap_sysctl_limit_mib(cap_sysctl_limit_t *limit __unused, + const int *mibp __unused, u_int miblen __unused, + int flags __unused) +{ + + return (NULL); +} + +static inline int +cap_sysctl_limit(cap_sysctl_limit_t *limit __unused) +{ + + return (0); +} +#endif /* WITH_CASPER */ + +#endif /* !_CAP_SYSCTL_H_ */ diff --git a/lib/libcasper/services/cap_sysctl/tests/Makefile b/lib/libcasper/services/cap_sysctl/tests/Makefile new file mode 100644 index 000000000000..85bb0d28c389 --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/tests/Makefile @@ -0,0 +1,17 @@ +.include <src.opts.mk> + +ATF_TESTS_C= sysctl_test + +.if ${MK_CASPER} != "no" +LIBADD+= casper +LIBADD+= cap_sysctl +CFLAGS+=-DWITH_CASPER +.endif +LIBADD+= nv + +# cap_sysctl tests modify global sysctl values and read them back, so +# cannot be run in parallel. +TEST_METADATA.sysctl_test+= required_user="root" \ + is_exclusive=true + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/cap_sysctl/tests/Makefile.depend b/lib/libcasper/services/cap_sysctl/tests/Makefile.depend new file mode 100644 index 000000000000..1938a0318d2c --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/tests/Makefile.depend @@ -0,0 +1,19 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/atf/libatf-c \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcasper/services/cap_sysctl \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_sysctl/tests/sysctl_test.c b/lib/libcasper/services/cap_sysctl/tests/sysctl_test.c new file mode 100644 index 000000000000..300333f11790 --- /dev/null +++ b/lib/libcasper/services/cap_sysctl/tests/sysctl_test.c @@ -0,0 +1,1689 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2013, 2018 The FreeBSD Foundation + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Portions of this software were developed by Mark Johnston + * under sponsorship from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/capsicum.h> +#include <sys/sysctl.h> +#include <sys/nv.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libcasper.h> +#include <casper/cap_sysctl.h> + +#include <atf-c.h> + +/* + * We need some sysctls to perform the tests on. + * We remember their values and restore them afer the test is done. + */ +#define SYSCTL0_PARENT "kern" +#define SYSCTL0_NAME "kern.sync_on_panic" +#define SYSCTL0_FILE "./sysctl0" +#define SYSCTL1_PARENT "debug" +#define SYSCTL1_NAME "debug.minidump" +#define SYSCTL1_FILE "./sysctl1" + +#define SYSCTL0_READ0 0x0001 +#define SYSCTL0_READ1 0x0002 +#define SYSCTL0_READ2 0x0004 +#define SYSCTL0_WRITE 0x0008 +#define SYSCTL0_READ_WRITE 0x0010 +#define SYSCTL1_READ0 0x0020 +#define SYSCTL1_READ1 0x0040 +#define SYSCTL1_READ2 0x0080 +#define SYSCTL1_WRITE 0x0100 +#define SYSCTL1_READ_WRITE 0x0200 + +static void +save_int_sysctl(const char *name, const char *file) +{ + ssize_t n; + size_t sz; + int error, fd, val; + + sz = sizeof(val); + error = sysctlbyname(name, &val, &sz, NULL, 0); + ATF_REQUIRE_MSG(error == 0, + "sysctlbyname(%s): %s", name, strerror(errno)); + + fd = open(file, O_CREAT | O_WRONLY, 0600); + ATF_REQUIRE_MSG(fd >= 0, "open(%s): %s", file, strerror(errno)); + n = write(fd, &val, sz); + ATF_REQUIRE(n >= 0 && (size_t)n == sz); + error = close(fd); + ATF_REQUIRE(error == 0); +} + +static void +restore_int_sysctl(const char *name, const char *file) +{ + ssize_t n; + size_t sz; + int error, fd, val; + + fd = open(file, O_RDONLY); + ATF_REQUIRE(fd >= 0); + sz = sizeof(val); + n = read(fd, &val, sz); + ATF_REQUIRE(n >= 0 && (size_t)n == sz); + error = unlink(file); + ATF_REQUIRE(error == 0); + error = close(fd); + ATF_REQUIRE(error == 0); + + error = sysctlbyname(name, NULL, NULL, &val, sz); + ATF_REQUIRE_MSG(error == 0, + "sysctlbyname(%s): %s", name, strerror(errno)); +} + +static cap_channel_t * +initcap(void) +{ + cap_channel_t *capcas, *capsysctl; + + save_int_sysctl(SYSCTL0_NAME, SYSCTL0_FILE); + save_int_sysctl(SYSCTL1_NAME, SYSCTL1_FILE); + + capcas = cap_init(); + ATF_REQUIRE(capcas != NULL); + + capsysctl = cap_service_open(capcas, "system.sysctl"); + ATF_REQUIRE(capsysctl != NULL); + + cap_close(capcas); + + return (capsysctl); +} + +static void +cleanup(void) +{ + + restore_int_sysctl(SYSCTL0_NAME, SYSCTL0_FILE); + restore_int_sysctl(SYSCTL1_NAME, SYSCTL1_FILE); +} + +static unsigned int +checkcaps(cap_channel_t *capsysctl) +{ + unsigned int result; + size_t len0, len1, oldsize; + int error, mib0[2], mib1[2], oldvalue, newvalue; + + result = 0; + + len0 = nitems(mib0); + ATF_REQUIRE(sysctlnametomib(SYSCTL0_NAME, mib0, &len0) == 0); + len1 = nitems(mib1); + ATF_REQUIRE(sysctlnametomib(SYSCTL1_NAME, mib1, &len1) == 0); + + oldsize = sizeof(oldvalue); + if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue, &oldsize, + NULL, 0) == 0) { + if (oldsize == sizeof(oldvalue)) + result |= SYSCTL0_READ0; + } + error = cap_sysctl(capsysctl, mib0, len0, &oldvalue, &oldsize, NULL, 0); + if ((result & SYSCTL0_READ0) != 0) + ATF_REQUIRE(error == 0); + else + ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0); + + newvalue = 123; + if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, NULL, NULL, &newvalue, + sizeof(newvalue)) == 0) { + result |= SYSCTL0_WRITE; + } + + if ((result & SYSCTL0_WRITE) != 0) { + oldsize = sizeof(oldvalue); + if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue, + &oldsize, NULL, 0) == 0) { + if (oldsize == sizeof(oldvalue) && oldvalue == 123) + result |= SYSCTL0_READ1; + } + } + newvalue = 123; + error = cap_sysctl(capsysctl, mib0, len0, NULL, NULL, + &newvalue, sizeof(newvalue)); + if ((result & SYSCTL0_WRITE) != 0) + ATF_REQUIRE(error == 0); + else + ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0); + + oldsize = sizeof(oldvalue); + newvalue = 4567; + if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue, &oldsize, + &newvalue, sizeof(newvalue)) == 0) { + if (oldsize == sizeof(oldvalue) && oldvalue == 123) + result |= SYSCTL0_READ_WRITE; + } + + if ((result & SYSCTL0_READ_WRITE) != 0) { + oldsize = sizeof(oldvalue); + if (cap_sysctlbyname(capsysctl, SYSCTL0_NAME, &oldvalue, + &oldsize, NULL, 0) == 0) { + if (oldsize == sizeof(oldvalue) && oldvalue == 4567) + result |= SYSCTL0_READ2; + } + } + + oldsize = sizeof(oldvalue); + if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue, &oldsize, + NULL, 0) == 0) { + if (oldsize == sizeof(oldvalue)) + result |= SYSCTL1_READ0; + } + error = cap_sysctl(capsysctl, mib1, len1, &oldvalue, &oldsize, NULL, 0); + if ((result & SYSCTL1_READ0) != 0) + ATF_REQUIRE(error == 0); + else + ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0); + + newvalue = 506; + if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, NULL, NULL, &newvalue, + sizeof(newvalue)) == 0) { + result |= SYSCTL1_WRITE; + } + + if ((result & SYSCTL1_WRITE) != 0) { + newvalue = 506; + ATF_REQUIRE(cap_sysctl(capsysctl, mib1, len1, NULL, NULL, + &newvalue, sizeof(newvalue)) == 0); + + oldsize = sizeof(oldvalue); + if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue, + &oldsize, NULL, 0) == 0) { + if (oldsize == sizeof(oldvalue) && oldvalue == 506) + result |= SYSCTL1_READ1; + } + } + newvalue = 506; + error = cap_sysctl(capsysctl, mib1, len1, NULL, NULL, + &newvalue, sizeof(newvalue)); + if ((result & SYSCTL1_WRITE) != 0) + ATF_REQUIRE(error == 0); + else + ATF_REQUIRE_ERRNO(ENOTCAPABLE, error != 0); + + oldsize = sizeof(oldvalue); + newvalue = 7008; + if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue, &oldsize, + &newvalue, sizeof(newvalue)) == 0) { + if (oldsize == sizeof(oldvalue) && oldvalue == 506) + result |= SYSCTL1_READ_WRITE; + } + + if ((result & SYSCTL1_READ_WRITE) != 0) { + oldsize = sizeof(oldvalue); + if (cap_sysctlbyname(capsysctl, SYSCTL1_NAME, &oldvalue, + &oldsize, NULL, 0) == 0) { + if (oldsize == sizeof(oldvalue) && oldvalue == 7008) + result |= SYSCTL1_READ2; + } + } + + return (result); +} + +ATF_TC_WITH_CLEANUP(cap_sysctl__operation); +ATF_TC_HEAD(cap_sysctl__operation, tc) +{ +} +ATF_TC_BODY(cap_sysctl__operation, tc) +{ + cap_channel_t *capsysctl, *ocapsysctl; + void *limit; + + ocapsysctl = initcap(); + + /* + * Allow: + * SYSCTL0_PARENT/RDWR/RECURSIVE + * SYSCTL1_PARENT/RDWR/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, "foo.bar", + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, "foo.bar", + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE | + SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE | + SYSCTL1_READ_WRITE)); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE | + SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE | + SYSCTL1_READ_WRITE)); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE)); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE)); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL0_READ0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/RDWR/RECURSIVE + * SYSCTL1_NAME/RDWR/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE | + SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE | + SYSCTL1_READ_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/RDWR + * SYSCTL1_PARENT/RDWR + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/RDWR + * SYSCTL1_NAME/RDWR + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE | + SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE | + SYSCTL1_READ_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/RDWR + * SYSCTL1_PARENT/RDWR/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL1_READ0 | SYSCTL1_READ1 | + SYSCTL1_READ2 | SYSCTL1_WRITE | SYSCTL1_READ_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/RDWR + * SYSCTL1_NAME/RDWR/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE | + SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE | + SYSCTL1_READ_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ/RECURSIVE + * SYSCTL1_PARENT/READ/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/READ/RECURSIVE + * SYSCTL1_NAME/READ/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ + * SYSCTL1_PARENT/READ + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/READ + * SYSCTL1_NAME/READ + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ + * SYSCTL1_PARENT/READ/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_READ0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/READ + * SYSCTL1_NAME/READ/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_READ0)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/WRITE/RECURSIVE + * SYSCTL1_PARENT/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/WRITE/RECURSIVE + * SYSCTL1_NAME/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/WRITE + * SYSCTL1_PARENT/WRITE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/WRITE + * SYSCTL1_NAME/WRITE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/WRITE + * SYSCTL1_PARENT/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/WRITE + * SYSCTL1_NAME/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_WRITE | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ/RECURSIVE + * SYSCTL1_PARENT/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/READ/RECURSIVE + * SYSCTL1_NAME/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ + * SYSCTL1_PARENT/WRITE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/READ + * SYSCTL1_NAME/WRITE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ + * SYSCTL1_PARENT/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_NAME/READ + * SYSCTL1_NAME/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL1_WRITE)); + + cap_close(capsysctl); +} +ATF_TC_CLEANUP(cap_sysctl__operation, tc) +{ + cleanup(); +} + +ATF_TC_WITH_CLEANUP(cap_sysctl__names); +ATF_TC_HEAD(cap_sysctl__names, tc) +{ +} +ATF_TC_BODY(cap_sysctl__names, tc) +{ + cap_channel_t *capsysctl, *ocapsysctl; + void *limit; + + ocapsysctl = initcap(); + + /* + * Allow: + * SYSCTL0_PARENT/READ/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL0_READ0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL1_NAME/READ/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_READ | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_READ0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL0_WRITE); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL1_NAME/WRITE/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_WRITE | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/RDWR/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL1_NAME/RDWR/RECURSIVE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL1_READ0 | SYSCTL1_READ1 | + SYSCTL1_READ2 | SYSCTL1_WRITE | SYSCTL1_READ_WRITE)); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/READ + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL1_NAME/READ + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_READ); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_READ0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/WRITE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL1_NAME/WRITE + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_WRITE); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == SYSCTL1_WRITE); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL0_PARENT/RDWR + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_PARENT, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_PARENT, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == 0); + + cap_close(capsysctl); + + /* + * Allow: + * SYSCTL1_NAME/RDWR + */ + + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + (void)cap_sysctl_limit_name(limit, SYSCTL1_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, SYSCTL0_NAME, CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == -1 && errno == ENOTCAPABLE); + + ATF_REQUIRE(checkcaps(capsysctl) == (SYSCTL1_READ0 | SYSCTL1_READ1 | + SYSCTL1_READ2 | SYSCTL1_WRITE | SYSCTL1_READ_WRITE)); + + cap_close(capsysctl); +} +ATF_TC_CLEANUP(cap_sysctl__names, tc) +{ + cleanup(); +} + +ATF_TC_WITH_CLEANUP(cap_sysctl__no_limits); +ATF_TC_HEAD(cap_sysctl__no_limits, tc) +{ +} +ATF_TC_BODY(cap_sysctl__no_limits, tc) +{ + cap_channel_t *capsysctl; + + capsysctl = initcap(); + + ATF_REQUIRE_EQ(checkcaps(capsysctl), (SYSCTL0_READ0 | SYSCTL0_READ1 | + SYSCTL0_READ2 | SYSCTL0_WRITE | SYSCTL0_READ_WRITE | + SYSCTL1_READ0 | SYSCTL1_READ1 | SYSCTL1_READ2 | SYSCTL1_WRITE | + SYSCTL1_READ_WRITE)); +} +ATF_TC_CLEANUP(cap_sysctl__no_limits, tc) +{ + cleanup(); +} + +ATF_TC_WITH_CLEANUP(cap_sysctl__recursive_limits); +ATF_TC_HEAD(cap_sysctl__recursive_limits, tc) +{ +} +ATF_TC_BODY(cap_sysctl__recursive_limits, tc) +{ + cap_channel_t *capsysctl, *ocapsysctl; + void *limit; + size_t len; + int mib[2], val = 420; + + len = nitems(mib); + ATF_REQUIRE(sysctlnametomib(SYSCTL0_NAME, mib, &len) == 0); + + ocapsysctl = initcap(); + + /* + * Make sure that we match entire components. + */ + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, "ker", + CAP_SYSCTL_RDWR | CAP_SYSCTL_RECURSIVE); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctlbyname(capsysctl, SYSCTL0_NAME, + NULL, NULL, &val, sizeof(val))); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctl(capsysctl, mib, len, + NULL, NULL, &val, sizeof(val))); + + cap_close(capsysctl); + + /* + * Verify that we check for CAP_SYSCTL_RECURSIVE. + */ + capsysctl = cap_clone(ocapsysctl); + ATF_REQUIRE(capsysctl != NULL); + + limit = cap_sysctl_limit_init(capsysctl); + (void)cap_sysctl_limit_name(limit, "kern", CAP_SYSCTL_RDWR); + ATF_REQUIRE(cap_sysctl_limit(limit) == 0); + + ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctlbyname(capsysctl, SYSCTL0_NAME, + NULL, NULL, &val, sizeof(val))); + ATF_REQUIRE_ERRNO(ENOTCAPABLE, cap_sysctl(capsysctl, mib, len, + NULL, NULL, &val, sizeof(val))); + + cap_close(capsysctl); +} +ATF_TC_CLEANUP(cap_sysctl__recursive_limits, tc) +{ + cleanup(); +} + +ATF_TC_WITH_CLEANUP(cap_sysctl__just_size); +ATF_TC_HEAD(cap_sysctl__just_size, tc) +{ +} +ATF_TC_BODY(cap_sysctl__just_size, tc) +{ + cap_channel_t *capsysctl; + size_t len; + int mib0[2]; + + capsysctl = initcap(); + + len = nitems(mib0); + ATF_REQUIRE(sysctlnametomib(SYSCTL0_NAME, mib0, &len) == 0); + + ATF_REQUIRE(cap_sysctlbyname(capsysctl, SYSCTL0_NAME, + NULL, &len, NULL, 0) == 0); + ATF_REQUIRE(len == sizeof(int)); + ATF_REQUIRE(cap_sysctl(capsysctl, mib0, nitems(mib0), + NULL, &len, NULL, 0) == 0); + ATF_REQUIRE(len == sizeof(int)); + + cap_close(capsysctl); +} +ATF_TC_CLEANUP(cap_sysctl__just_size, tc) +{ + cleanup(); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, cap_sysctl__operation); + ATF_TP_ADD_TC(tp, cap_sysctl__names); + ATF_TP_ADD_TC(tp, cap_sysctl__no_limits); + ATF_TP_ADD_TC(tp, cap_sysctl__recursive_limits); + ATF_TP_ADD_TC(tp, cap_sysctl__just_size); + + return (atf_no_error()); +} diff --git a/lib/libcasper/services/cap_syslog/Makefile b/lib/libcasper/services/cap_syslog/Makefile new file mode 100644 index 000000000000..88979d8bed23 --- /dev/null +++ b/lib/libcasper/services/cap_syslog/Makefile @@ -0,0 +1,30 @@ +SHLIBDIR?= /lib + +.include <src.opts.mk> + +PACKAGE= runtime + +SHLIB_MAJOR= 1 +INCSDIR?= ${INCLUDEDIR}/casper + +.if ${MK_CASPER} != "no" +SHLIB= cap_syslog + +SRCS= cap_syslog.c +.endif + +INCS= cap_syslog.h + +LIBADD= nv + +CFLAGS+=-I${.CURDIR} + +MAN+= cap_syslog.3 + +MLINKS+= cap_syslog.3 libcap_syslog.3 +MLINKS+= cap_syslog.3 cap_vsyslog.3 +MLINKS+= cap_syslog.3 cap_openlog.3 +MLINKS+= cap_syslog.3 cap_closelog.3 +MLINKS+= cap_syslog.3 cap_setlogmask.3 + +.include <bsd.lib.mk> diff --git a/lib/libcasper/services/cap_syslog/Makefile.depend b/lib/libcasper/services/cap_syslog/Makefile.depend new file mode 100644 index 000000000000..02bae00eb04d --- /dev/null +++ b/lib/libcasper/services/cap_syslog/Makefile.depend @@ -0,0 +1,17 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcasper/libcasper \ + lib/libcompiler_rt \ + lib/libnv \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/services/cap_syslog/cap_syslog.3 b/lib/libcasper/services/cap_syslog/cap_syslog.3 new file mode 100644 index 000000000000..4d6463ef3f81 --- /dev/null +++ b/lib/libcasper/services/cap_syslog/cap_syslog.3 @@ -0,0 +1,115 @@ +.\" Copyright (c) 2018 Mariusz Zaborski <oshogbo@FreeBSD.org> +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 6, 2023 +.Dt CAP_SYSLOG 3 +.Os +.Sh NAME +.Nm cap_syslog , +.Nm cap_vsyslog , +.Nm cap_openlog , +.Nm cap_closelog , +.Nm cap_setlogmask +.Nd "library for syslog in capability mode" +.Sh LIBRARY +.Lb libcap_syslog +.Sh SYNOPSIS +.In libcasper.h +.In casper/cap_syslog.h +.Ft void +.Fn cap_syslog "cap_channel_t *chan" "int pri" "const char *fmt" "..." +.Ft void +.Fn cap_vsyslog "cap_channel_t *chan" "int priority" "const char *fmt" "va_list ap" +.Ft void +.Fn cap_openlog "cap_channel_t *chan" "const char *ident" "int logopt" "int facility" +.Ft void +.Fn cap_closelog "cap_channel_t *chan" +.Ft int +.Fn cap_setlogmask "cap_channel_t *chan" "int maskpri" +.Sh DESCRIPTION +The functions +.Fn cap_syslog +.Fn cap_vsyslog +.Fn cap_openlog +.Fn cap_closelog +.Fn cap_setlogmask +are respectively equivalent to +.Xr syslog 3 , +.Xr vsyslog 3 , +.Xr openlog 3 , +.Xr closelog 3 , +.Xr setlogmask 3 +except that the connection to the +.Nm system.syslog +service needs to be provided. +.Pp +All of these functions are reentrant but not thread-safe. +That is, they may be called from separate threads only with different +.Vt cap_channel_t +arguments or with synchronization. +.Sh EXAMPLES +The following example first opens a capability to casper and then uses this +capability to create the +.Nm system.syslog +casper service to log messages. +.Bd -literal +cap_channel_t *capcas, *capsyslog; + +/* Open capability to Casper. */ +capcas = cap_init(); +if (capcas == NULL) + err(1, "Unable to contact Casper"); + +/* Enter capability mode sandbox. */ +if (cap_enter() < 0 && errno != ENOSYS) + err(1, "Unable to enter capability mode"); + +/* Use Casper capability to create capability to the system.syslog service. */ +capsyslog = cap_service_open(capcas, "system.syslog"); +if (capsyslog == NULL) + err(1, "Unable to open system.syslog service"); + +/* Close Casper capability, we don't need it anymore. */ +cap_close(capcas); + +/* Let's log something. */ +cap_syslog(capsyslog, LOG_NOTICE, "System logs from capability mode."); +.Ed +.Sh SEE ALSO +.Xr cap_enter 2 , +.Xr closelog 3 , +.Xr err 3 , +.Xr openlog 3 , +.Xr setlogmask 3 , +.Xr syslog 3 , +.Xr vsyslog 3 , +.Xr capsicum 4 , +.Xr nv 9 +.Sh HISTORY +The +.Nm cap_syslog +service first appeared in +.Fx 10.3 . +.Sh AUTHORS +.An Mariusz Zaborski Aq Mt oshogbo@FreeBSD.org diff --git a/lib/libcasper/services/cap_syslog/cap_syslog.c b/lib/libcasper/services/cap_syslog/cap_syslog.c new file mode 100644 index 000000000000..5434b7bb6bab --- /dev/null +++ b/lib/libcasper/services/cap_syslog/cap_syslog.c @@ -0,0 +1,222 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2017 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +#include <sys/dnv.h> +#include <sys/nv.h> + +#include <assert.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include <libcasper.h> +#include <libcasper_service.h> + +#include "cap_syslog.h" + +#define CAP_SYSLOG_LIMIT 2048 + +void +cap_syslog(cap_channel_t *chan, int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + cap_vsyslog(chan, pri, fmt, ap); + va_end(ap); +} + +void +cap_vsyslog(cap_channel_t *chan, int priority, const char *fmt, va_list ap) +{ + nvlist_t *nvl; + char message[CAP_SYSLOG_LIMIT]; + + (void)vsnprintf(message, sizeof(message), fmt, ap); + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "vsyslog"); + nvlist_add_number(nvl, "priority", priority); + nvlist_add_string(nvl, "message", message); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + return; + } + nvlist_destroy(nvl); +} + +void +cap_openlog(cap_channel_t *chan, const char *ident, int logopt, int facility) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "openlog"); + if (ident != NULL) { + nvlist_add_string(nvl, "ident", ident); + } + nvlist_add_number(nvl, "logopt", logopt); + nvlist_add_number(nvl, "facility", facility); + if (logopt & LOG_PERROR) { + nvlist_add_descriptor(nvl, "stderr", STDERR_FILENO); + } + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + return; + } + nvlist_destroy(nvl); +} + +void +cap_closelog(cap_channel_t *chan) +{ + nvlist_t *nvl; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "closelog"); + nvl = cap_xfer_nvlist(chan, nvl); + if (nvl == NULL) { + return; + } + nvlist_destroy(nvl); +} + +int +cap_setlogmask(cap_channel_t *chan, int maskpri) +{ + nvlist_t *nvl; + int omask; + + nvl = nvlist_create(0); + nvlist_add_string(nvl, "cmd", "setlogmask"); + nvlist_add_number(nvl, "maskpri", maskpri); + nvl = cap_xfer_nvlist(chan, nvl); + omask = nvlist_get_number(nvl, "omask"); + + nvlist_destroy(nvl); + + return (omask); +} + +/* + * Service functions. + */ + +static char *LogTag; +static int prev_stderr = -1; + +static void +slog_vsyslog(const nvlist_t *limits __unused, const nvlist_t *nvlin, + nvlist_t *nvlout __unused) +{ + + syslog(nvlist_get_number(nvlin, "priority"), "%s", + nvlist_get_string(nvlin, "message")); +} + +static void +slog_openlog(const nvlist_t *limits __unused, const nvlist_t *nvlin, + nvlist_t *nvlout __unused) +{ + const char *ident; + uint64_t logopt; + int stderr_fd; + + ident = dnvlist_get_string(nvlin, "ident", NULL); + if (ident != NULL) { + free(LogTag); + LogTag = strdup(ident); + } + + logopt = nvlist_get_number(nvlin, "logopt"); + if (logopt & LOG_PERROR) { + stderr_fd = dnvlist_get_descriptor(nvlin, "stderr", -1); + if (prev_stderr == -1) + prev_stderr = dup(STDERR_FILENO); + if (prev_stderr != -1) + (void)dup2(stderr_fd, STDERR_FILENO); + } else if (prev_stderr != -1) { + (void)dup2(prev_stderr, STDERR_FILENO); + close(prev_stderr); + prev_stderr = -1; + } + openlog(LogTag, logopt, nvlist_get_number(nvlin, "facility")); +} + +static void +slog_closelog(const nvlist_t *limits __unused, const nvlist_t *nvlin __unused, + nvlist_t *nvlout __unused) +{ + + closelog(); + + free(LogTag); + LogTag = NULL; + + if (prev_stderr != -1) { + (void)dup2(prev_stderr, STDERR_FILENO); + close(prev_stderr); + prev_stderr = -1; + } +} + +static void +slog_setlogmask(const nvlist_t *limits __unused, const nvlist_t *nvlin, + nvlist_t *nvlout) +{ + int omask; + + omask = setlogmask(nvlist_get_number(nvlin, "maskpri")); + nvlist_add_number(nvlout, "omask", omask); +} + +static int +syslog_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, + nvlist_t *nvlout) +{ + + if (strcmp(cmd, "vsyslog") == 0) { + slog_vsyslog(limits, nvlin, nvlout); + } else if (strcmp(cmd, "openlog") == 0) { + slog_openlog(limits, nvlin, nvlout); + } else if (strcmp(cmd, "closelog") == 0) { + slog_closelog(limits, nvlin, nvlout); + } else if (strcmp(cmd, "setlogmask") == 0) { + slog_setlogmask(limits, nvlin, nvlout); + } else { + return (EINVAL); + } + + return (0); +} + +CREATE_SERVICE("system.syslog", NULL, syslog_command, 0); diff --git a/lib/libcasper/services/cap_syslog/cap_syslog.h b/lib/libcasper/services/cap_syslog/cap_syslog.h new file mode 100644 index 000000000000..ac546664f16c --- /dev/null +++ b/lib/libcasper/services/cap_syslog/cap_syslog.h @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 2017 Mariusz Zaborski <oshogbo@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _CAP_SYSLOG_H_ +#define _CAP_SYSLOG_H_ + +#include <sys/cdefs.h> + +#ifdef WITH_CASPER +__BEGIN_DECLS + +void cap_syslog(cap_channel_t *chan, int pri, + const char *fmt, ...) __printflike(3, 4); +void cap_vsyslog(cap_channel_t *chan, int priority, const char *fmt, + va_list ap) __printflike(3, 0); + +void cap_openlog(cap_channel_t *chan, const char *ident, int logopt, + int facility); +void cap_closelog(cap_channel_t *chan); + +int cap_setlogmask(cap_channel_t *chan, int maskpri); + +__END_DECLS + +#else +#define cap_syslog(chan, pri, ...) syslog(pri, __VA_ARGS__) +#define cap_vsyslog(chan, pri, fmt, ap) vsyslog(pri, fmt, ap) + +#define cap_openlog(chan, ident, logopt, facility) \ + openlog(ident, logopt, facility) +#define cap_closelog(chan) closelog() + +#define cap_setlogmask(chan, maskpri) setlogmask(maskpri) +#endif /* !WITH_CASPER */ + +#endif /* !_CAP_SYSLOG_H_ */ diff --git a/lib/libcasper/services/tests/Makefile b/lib/libcasper/services/tests/Makefile new file mode 100644 index 000000000000..29b1b564beca --- /dev/null +++ b/lib/libcasper/services/tests/Makefile @@ -0,0 +1,4 @@ +.PATH: ${SRCTOP}/tests +KYUAFILE= yes + +.include <bsd.test.mk> diff --git a/lib/libcasper/services/tests/Makefile.depend b/lib/libcasper/services/tests/Makefile.depend new file mode 100644 index 000000000000..11aba52f82cf --- /dev/null +++ b/lib/libcasper/services/tests/Makefile.depend @@ -0,0 +1,10 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/lib/libcasper/tests/Makefile b/lib/libcasper/tests/Makefile new file mode 100644 index 000000000000..29b1b564beca --- /dev/null +++ b/lib/libcasper/tests/Makefile @@ -0,0 +1,4 @@ +.PATH: ${SRCTOP}/tests +KYUAFILE= yes + +.include <bsd.test.mk> |