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 #include <sys/types.h> 33 #include <sys/capsicum.h> 34 #include <sys/dnv.h> 35 #include <sys/nv.h> 36 #include <sys/queue.h> 37 #include <sys/wait.h> 38 39 #include <net/if.h> 40 #include <netinet/in.h> 41 42 #include <capsicum_helpers.h> 43 #include <errno.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 48 #include <libcasper.h> 49 #include <libcasper_service.h> 50 51 #include "rtsold.h" 52 53 /* 54 * Run the script and return the write end of a pipe to the main process. 55 * Return -1 and set errno on error. 56 */ 57 static int 58 script_run(char **argv) 59 { 60 pid_t pid; 61 int fd[2], null; 62 63 if (pipe(fd) != 0) 64 return (-1); 65 if ((pid = fork()) < 0) 66 return (-1); 67 if (pid == 0) { 68 (void)close(fd[1]); 69 null = open("/dev/null", O_RDWR); 70 if (null < 0) 71 _exit(1); 72 if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) 73 _exit(1); 74 75 closefrom(3); 76 (void)execve(argv[0], argv, NULL); 77 _exit(1); 78 } else 79 (void)close(fd[0]); 80 81 return (fd[1]); 82 } 83 84 int 85 cap_script_run(cap_channel_t *cap, const char *const *argv) 86 { 87 #ifdef WITH_CASPER 88 nvlist_t *nvl; 89 size_t argc; 90 int error, wfd; 91 92 for (argc = 0; argv[argc] != NULL; argc++) 93 ; 94 95 nvl = nvlist_create(0); 96 nvlist_add_string(nvl, "cmd", "script_run"); 97 nvlist_add_string_array(nvl, "argv", argv, argc); 98 nvl = cap_xfer_nvlist(cap, nvl); 99 if (nvl == NULL) 100 return (-1); 101 102 error = (int)dnvlist_get_number(nvl, "error", 0); 103 if (error == 0) 104 wfd = nvlist_take_descriptor(nvl, "fd"); 105 nvlist_destroy(nvl); 106 if (error != 0) 107 errno = error; 108 return (error == 0 ? wfd : -1); 109 #else 110 (void)cap; 111 return (script_run(__DECONST(char **, argv))); 112 #endif 113 } 114 115 /* 116 * Wait for a child process to exit, and return its status. 117 * Return -1 and set errno upon error. 118 */ 119 static int 120 script_wait(int *statusp) 121 { 122 int error; 123 124 error = wait(statusp); 125 return (error >= 0 ? 0 : -1); 126 } 127 128 int 129 cap_script_wait(cap_channel_t *cap, int *statusp) 130 { 131 #ifdef WITH_CASPER 132 nvlist_t *nvl; 133 int error; 134 135 nvl = nvlist_create(0); 136 nvlist_add_string(nvl, "cmd", "script_wait"); 137 nvl = cap_xfer_nvlist(cap, nvl); 138 if (nvl == NULL) 139 return (-1); 140 141 error = (int)dnvlist_get_number(nvl, "error", 0); 142 if (error == 0) 143 *statusp = (int)nvlist_get_number(nvl, "status"); 144 nvlist_destroy(nvl); 145 if (error != 0) 146 errno = error; 147 return (error == 0 ? 0 : -1); 148 #else 149 (void)cap; 150 return (script_wait(statusp)); 151 #endif 152 } 153 154 #ifdef WITH_CASPER 155 static int 156 script_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, 157 nvlist_t *nvlout) 158 { 159 cap_rights_t rights; 160 const char *const *iargv, *const *scripts; 161 char **argv; 162 size_t argc, i, nscripts; 163 int error, fd, status; 164 165 if (strcmp(cmd, "script_wait") == 0) { 166 /* Wait for the result of a previous "script_run" command. */ 167 if (script_wait(&status) == -1) 168 return (errno); 169 nvlist_add_number(nvlout, "status", status); 170 return (0); 171 } 172 if (strcmp(cmd, "script_run") != 0) 173 return (EINVAL); 174 175 /* 176 * Validate the argv against the limits specified at initialization 177 * time. 178 */ 179 iargv = nvlist_get_string_array(nvlin, "argv", &argc); 180 if (argc == 0) 181 return (EINVAL); 182 scripts = nvlist_get_string_array(limits, "scripts", &nscripts); 183 for (i = 0; i < nscripts; i++) 184 if (strcmp(iargv[0], scripts[i]) == 0) 185 break; 186 if (i == nscripts) 187 return (EINVAL); 188 189 /* 190 * The nvlist API does not permit NULL pointers in an array, so we have 191 * to add the nul terminator ourselves. Yuck. 192 */ 193 argv = calloc(argc + 1, sizeof(*argv)); 194 if (argv == NULL) 195 return (errno); 196 memcpy(argv, iargv, sizeof(*argv) * argc); 197 198 fd = script_run(argv); 199 error = errno; 200 free(argv); 201 if (fd < 0) 202 return (error); 203 204 (void)caph_rights_limit(fd, cap_rights_init(&rights, CAP_WRITE)); 205 nvlist_move_descriptor(nvlout, "fd", fd); 206 return (0); 207 } 208 209 static int 210 script_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 211 { 212 const char *name; 213 void *cookie; 214 int nvtype; 215 bool hasscripts; 216 217 /* Limits may only be set once. */ 218 if (oldlimits != NULL) 219 return (ENOTCAPABLE); 220 221 cookie = NULL; 222 hasscripts = false; 223 while ((name = nvlist_next(newlimits, &nvtype, &cookie)) != NULL) { 224 if (nvtype == NV_TYPE_STRING_ARRAY && 225 strcmp(name, "scripts") == 0) 226 hasscripts = true; 227 else 228 return (EINVAL); 229 } 230 if (!hasscripts) 231 return (EINVAL); 232 return (0); 233 } 234 235 CREATE_SERVICE("rtsold.script", script_limit, script_command, 0); 236 #endif /* WITH_CASPER */ 237