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