1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2018 The FreeBSD Foundation 5 * 6 * This software was developed by Mark Johnston under sponsorship from 7 * the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions are 11 * met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/types.h> 35 #include <sys/capsicum.h> 36 #include <sys/dnv.h> 37 #include <sys/nv.h> 38 #include <sys/queue.h> 39 #include <sys/wait.h> 40 41 #include <net/if.h> 42 #include <netinet/in.h> 43 44 #include <capsicum_helpers.h> 45 #include <errno.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include <libcasper.h> 51 #include <libcasper_service.h> 52 53 #include "rtsold.h" 54 55 /* 56 * Run the script and return the write end of a pipe to the main process. 57 * Return -1 and set errno on error. 58 */ 59 static int 60 script_run(char **argv) 61 { 62 pid_t pid; 63 int fd[2], null; 64 65 if (pipe(fd) != 0) 66 return (-1); 67 if ((pid = fork()) < 0) 68 return (-1); 69 if (pid == 0) { 70 (void)close(fd[1]); 71 null = open("/dev/null", O_RDWR); 72 if (null < 0) 73 _exit(1); 74 if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) 75 _exit(1); 76 77 closefrom(3); 78 (void)execve(argv[0], argv, NULL); 79 _exit(1); 80 } else 81 (void)close(fd[0]); 82 83 return (fd[1]); 84 } 85 86 int 87 cap_script_run(cap_channel_t *cap, const char *const *argv) 88 { 89 #ifdef WITH_CASPER 90 nvlist_t *nvl; 91 size_t argc; 92 int error, wfd; 93 94 for (argc = 0; argv[argc] != NULL; argc++) 95 ; 96 97 nvl = nvlist_create(0); 98 nvlist_add_string(nvl, "cmd", "script_run"); 99 nvlist_add_string_array(nvl, "argv", argv, argc); 100 nvl = cap_xfer_nvlist(cap, nvl); 101 if (nvl == NULL) 102 return (-1); 103 104 error = (int)dnvlist_get_number(nvl, "error", 0); 105 if (error == 0) 106 wfd = nvlist_take_descriptor(nvl, "fd"); 107 nvlist_destroy(nvl); 108 if (error != 0) 109 errno = error; 110 return (error == 0 ? wfd : -1); 111 #else 112 (void)cap; 113 return (script_run(__DECONST(char **, argv))); 114 #endif 115 } 116 117 /* 118 * Wait for a child process to exit, and return its status. 119 * Return -1 and set errno upon error. 120 */ 121 static int 122 script_wait(int *statusp) 123 { 124 int error; 125 126 error = wait(statusp); 127 return (error >= 0 ? 0 : -1); 128 } 129 130 int 131 cap_script_wait(cap_channel_t *cap, int *statusp) 132 { 133 #ifdef WITH_CASPER 134 nvlist_t *nvl; 135 int error; 136 137 nvl = nvlist_create(0); 138 nvlist_add_string(nvl, "cmd", "script_wait"); 139 nvl = cap_xfer_nvlist(cap, nvl); 140 if (nvl == NULL) 141 return (-1); 142 143 error = (int)dnvlist_get_number(nvl, "error", 0); 144 if (error == 0) 145 *statusp = (int)nvlist_get_number(nvl, "status"); 146 nvlist_destroy(nvl); 147 if (error != 0) 148 errno = error; 149 return (error == 0 ? 0 : -1); 150 #else 151 (void)cap; 152 return (script_wait(statusp)); 153 #endif 154 } 155 156 #ifdef WITH_CASPER 157 static int 158 script_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, 159 nvlist_t *nvlout) 160 { 161 cap_rights_t rights; 162 const char *const *iargv, *const *scripts; 163 char **argv; 164 size_t argc, i, nscripts; 165 int error, fd, status; 166 167 if (strcmp(cmd, "script_wait") == 0) { 168 /* Wait for the result of a previous "script_run" command. */ 169 if (script_wait(&status) == -1) 170 return (errno); 171 nvlist_add_number(nvlout, "status", status); 172 return (0); 173 } 174 if (strcmp(cmd, "script_run") != 0) 175 return (EINVAL); 176 177 /* 178 * Validate the argv against the limits specified at initialization 179 * time. 180 */ 181 iargv = nvlist_get_string_array(nvlin, "argv", &argc); 182 if (argc == 0) 183 return (EINVAL); 184 scripts = nvlist_get_string_array(limits, "scripts", &nscripts); 185 for (i = 0; i < nscripts; i++) 186 if (strcmp(iargv[0], scripts[i]) == 0) 187 break; 188 if (i == nscripts) 189 return (EINVAL); 190 191 /* 192 * The nvlist API does not permit NULL pointers in an array, so we have 193 * to add the nul terminator ourselves. Yuck. 194 */ 195 argv = calloc(argc + 1, sizeof(*argv)); 196 if (argv == NULL) 197 return (errno); 198 memcpy(argv, iargv, sizeof(*argv) * argc); 199 200 fd = script_run(argv); 201 error = errno; 202 free(argv); 203 if (fd < 0) 204 return (error); 205 206 (void)caph_rights_limit(fd, cap_rights_init(&rights, CAP_WRITE)); 207 nvlist_move_descriptor(nvlout, "fd", fd); 208 return (0); 209 } 210 211 static int 212 script_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 213 { 214 const char *name; 215 void *cookie; 216 int nvtype; 217 bool hasscripts; 218 219 /* Limits may only be set once. */ 220 if (oldlimits != NULL) 221 return (ENOTCAPABLE); 222 223 cookie = NULL; 224 hasscripts = false; 225 while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) { 226 if (nvtype == NV_TYPE_STRING_ARRAY && 227 strcmp(name, "scripts") == 0) 228 hasscripts = true; 229 else 230 return (EINVAL); 231 } 232 if (!hasscripts) 233 return (EINVAL); 234 return (0); 235 } 236 237 CREATE_SERVICE("rtsold.script", script_limit, script_command, 0); 238 #endif /* WITH_CASPER */ 239