1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2002 Tim J. Robbins. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include "namespace.h" 30 #include <sys/cdefs.h> 31 #include <sys/types.h> 32 #include <sys/wait.h> 33 #include <errno.h> 34 #include <fcntl.h> 35 #include <paths.h> 36 #include <signal.h> 37 #include <stdbool.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <wordexp.h> 43 #include "un-namespace.h" 44 #include "libc_private.h" 45 46 __FBSDID("$FreeBSD$"); 47 48 static int we_askshell(const char *, wordexp_t *, int); 49 static int we_check(const char *); 50 51 /* 52 * wordexp -- 53 * Perform shell word expansion on `words' and place the resulting list 54 * of words in `we'. See wordexp(3). 55 * 56 * Specified by IEEE Std. 1003.1-2001. 57 */ 58 int 59 wordexp(const char * __restrict words, wordexp_t * __restrict we, int flags) 60 { 61 int error; 62 63 if (flags & WRDE_REUSE) 64 wordfree(we); 65 if ((flags & WRDE_APPEND) == 0) { 66 we->we_wordc = 0; 67 we->we_wordv = NULL; 68 we->we_strings = NULL; 69 we->we_nbytes = 0; 70 } 71 if ((error = we_check(words)) != 0) { 72 wordfree(we); 73 return (error); 74 } 75 if ((error = we_askshell(words, we, flags)) != 0) { 76 wordfree(we); 77 return (error); 78 } 79 return (0); 80 } 81 82 static size_t 83 we_read_fully(int fd, char *buffer, size_t len) 84 { 85 size_t done; 86 ssize_t nread; 87 88 done = 0; 89 do { 90 nread = _read(fd, buffer + done, len - done); 91 if (nread == -1 && errno == EINTR) 92 continue; 93 if (nread <= 0) 94 break; 95 done += nread; 96 } while (done != len); 97 return done; 98 } 99 100 static bool 101 we_write_fully(int fd, const char *buffer, size_t len) 102 { 103 size_t done; 104 ssize_t nwritten; 105 106 done = 0; 107 do { 108 nwritten = _write(fd, buffer + done, len - done); 109 if (nwritten == -1 && errno == EINTR) 110 continue; 111 if (nwritten <= 0) 112 return (false); 113 done += nwritten; 114 } while (done != len); 115 return (true); 116 } 117 118 /* 119 * we_askshell -- 120 * Use the `freebsd_wordexp' /bin/sh builtin function to do most of the 121 * work in expanding the word string. This function is complicated by 122 * memory management. 123 */ 124 static int 125 we_askshell(const char *words, wordexp_t *we, int flags) 126 { 127 int pdesw[2]; /* Pipe for writing words */ 128 int pdes[2]; /* Pipe for reading output */ 129 char wfdstr[sizeof(int) * 3 + 1]; 130 char buf[35]; /* Buffer for byte and word count */ 131 long nwords, nbytes; /* Number of words, bytes from child */ 132 long i; /* Handy integer */ 133 size_t sofs; /* Offset into we->we_strings */ 134 size_t vofs; /* Offset into we->we_wordv */ 135 pid_t pid; /* Process ID of child */ 136 pid_t wpid; /* waitpid return value */ 137 int status; /* Child exit status */ 138 int error; /* Our return value */ 139 int serrno; /* errno to return */ 140 char *np, *p; /* Handy pointers */ 141 char *nstrings; /* Temporary for realloc() */ 142 char **nwv; /* Temporary for realloc() */ 143 sigset_t newsigblock, oldsigblock; 144 const char *ifs; 145 146 serrno = errno; 147 ifs = getenv("IFS"); 148 149 if (pipe2(pdesw, O_CLOEXEC) < 0) 150 return (WRDE_NOSPACE); /* XXX */ 151 snprintf(wfdstr, sizeof(wfdstr), "%d", pdesw[0]); 152 if (pipe2(pdes, O_CLOEXEC) < 0) { 153 _close(pdesw[0]); 154 _close(pdesw[1]); 155 return (WRDE_NOSPACE); /* XXX */ 156 } 157 (void)sigemptyset(&newsigblock); 158 (void)sigaddset(&newsigblock, SIGCHLD); 159 (void)__libc_sigprocmask(SIG_BLOCK, &newsigblock, &oldsigblock); 160 if ((pid = fork()) < 0) { 161 serrno = errno; 162 _close(pdesw[0]); 163 _close(pdesw[1]); 164 _close(pdes[0]); 165 _close(pdes[1]); 166 (void)__libc_sigprocmask(SIG_SETMASK, &oldsigblock, NULL); 167 errno = serrno; 168 return (WRDE_NOSPACE); /* XXX */ 169 } 170 else if (pid == 0) { 171 /* 172 * We are the child; make /bin/sh expand `words'. 173 */ 174 (void)__libc_sigprocmask(SIG_SETMASK, &oldsigblock, NULL); 175 if ((pdes[1] != STDOUT_FILENO ? 176 _dup2(pdes[1], STDOUT_FILENO) : 177 _fcntl(pdes[1], F_SETFD, 0)) < 0) 178 _exit(1); 179 if (_fcntl(pdesw[0], F_SETFD, 0) < 0) 180 _exit(1); 181 execl(_PATH_BSHELL, "sh", flags & WRDE_UNDEF ? "-u" : "+u", 182 "-c", "IFS=$1;eval \"$2\";" 183 "freebsd_wordexp -f \"$3\" ${4:+\"$4\"}", 184 "", 185 ifs != NULL ? ifs : " \t\n", 186 flags & WRDE_SHOWERR ? "" : "exec 2>/dev/null", 187 wfdstr, 188 flags & WRDE_NOCMD ? "-p" : "", 189 (char *)NULL); 190 _exit(1); 191 } 192 193 /* 194 * We are the parent; write the words. 195 */ 196 _close(pdes[1]); 197 _close(pdesw[0]); 198 if (!we_write_fully(pdesw[1], words, strlen(words))) { 199 _close(pdesw[1]); 200 error = WRDE_SYNTAX; 201 goto cleanup; 202 } 203 _close(pdesw[1]); 204 /* 205 * Read the output of the shell wordexp function, 206 * which is a byte indicating that the words were parsed successfully, 207 * a 64-bit hexadecimal word count, a dummy byte, a 64-bit hexadecimal 208 * byte count (not including terminating null bytes), followed by the 209 * expanded words separated by nulls. 210 */ 211 switch (we_read_fully(pdes[0], buf, 34)) { 212 case 1: 213 error = buf[0] == 'C' ? WRDE_CMDSUB : WRDE_BADVAL; 214 serrno = errno; 215 goto cleanup; 216 case 34: 217 break; 218 default: 219 error = WRDE_SYNTAX; 220 serrno = errno; 221 goto cleanup; 222 } 223 buf[17] = '\0'; 224 nwords = strtol(buf + 1, NULL, 16); 225 buf[34] = '\0'; 226 nbytes = strtol(buf + 18, NULL, 16) + nwords; 227 228 /* 229 * Allocate or reallocate (when flags & WRDE_APPEND) the word vector 230 * and string storage buffers for the expanded words we're about to 231 * read from the child. 232 */ 233 sofs = we->we_nbytes; 234 vofs = we->we_wordc; 235 if ((flags & (WRDE_DOOFFS|WRDE_APPEND)) == (WRDE_DOOFFS|WRDE_APPEND)) 236 vofs += we->we_offs; 237 we->we_wordc += nwords; 238 we->we_nbytes += nbytes; 239 if ((nwv = reallocarray(we->we_wordv, (we->we_wordc + 1 + 240 (flags & WRDE_DOOFFS ? we->we_offs : 0)), 241 sizeof(char *))) == NULL) { 242 error = WRDE_NOSPACE; 243 goto cleanup; 244 } 245 we->we_wordv = nwv; 246 if ((nstrings = realloc(we->we_strings, we->we_nbytes)) == NULL) { 247 error = WRDE_NOSPACE; 248 goto cleanup; 249 } 250 for (i = 0; i < vofs; i++) 251 if (we->we_wordv[i] != NULL) 252 we->we_wordv[i] += nstrings - we->we_strings; 253 we->we_strings = nstrings; 254 255 if (we_read_fully(pdes[0], we->we_strings + sofs, nbytes) != nbytes) { 256 error = WRDE_NOSPACE; /* abort for unknown reason */ 257 serrno = errno; 258 goto cleanup; 259 } 260 261 error = 0; 262 cleanup: 263 _close(pdes[0]); 264 do 265 wpid = _waitpid(pid, &status, 0); 266 while (wpid < 0 && errno == EINTR); 267 (void)__libc_sigprocmask(SIG_SETMASK, &oldsigblock, NULL); 268 if (error != 0) { 269 errno = serrno; 270 return (error); 271 } 272 if (wpid < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) 273 return (WRDE_NOSPACE); /* abort for unknown reason */ 274 275 /* 276 * Break the null-terminated expanded word strings out into 277 * the vector. 278 */ 279 if (vofs == 0 && flags & WRDE_DOOFFS) 280 while (vofs < we->we_offs) 281 we->we_wordv[vofs++] = NULL; 282 p = we->we_strings + sofs; 283 while (nwords-- != 0) { 284 we->we_wordv[vofs++] = p; 285 if ((np = memchr(p, '\0', nbytes)) == NULL) 286 return (WRDE_NOSPACE); /* XXX */ 287 nbytes -= np - p + 1; 288 p = np + 1; 289 } 290 we->we_wordv[vofs] = NULL; 291 292 return (0); 293 } 294 295 /* 296 * we_check -- 297 * Check that the string contains none of the following unquoted 298 * special characters: <newline> |&;<>(){} 299 * This mainly serves for {} which are normally legal in sh. 300 * It deliberately does not attempt to model full sh syntax. 301 */ 302 static int 303 we_check(const char *words) 304 { 305 char c; 306 /* Saw \ or $, possibly not special: */ 307 bool quote = false, dollar = false; 308 /* Saw ', ", ${, ` or $(, possibly not special: */ 309 bool have_sq = false, have_dq = false, have_par_begin = false; 310 bool have_cmd = false; 311 /* Definitely saw a ', ", ${, ` or $(, need a closing character: */ 312 bool need_sq = false, need_dq = false, need_par_end = false; 313 bool need_cmd_old = false, need_cmd_new = false; 314 315 while ((c = *words++) != '\0') { 316 switch (c) { 317 case '\\': 318 quote = !quote; 319 continue; 320 case '$': 321 if (quote) 322 quote = false; 323 else 324 dollar = !dollar; 325 continue; 326 case '\'': 327 if (!quote && !have_sq && !have_dq) 328 need_sq = true; 329 else 330 need_sq = false; 331 have_sq = true; 332 break; 333 case '"': 334 if (!quote && !have_sq && !have_dq) 335 need_dq = true; 336 else 337 need_dq = false; 338 have_dq = true; 339 break; 340 case '`': 341 if (!quote && !have_sq && !have_cmd) 342 need_cmd_old = true; 343 else 344 need_cmd_old = false; 345 have_cmd = true; 346 break; 347 case '{': 348 if (!quote && !dollar && !have_sq && !have_dq && 349 !have_cmd) 350 return (WRDE_BADCHAR); 351 if (dollar) { 352 if (!quote && !have_sq) 353 need_par_end = true; 354 have_par_begin = true; 355 } 356 break; 357 case '}': 358 if (!quote && !have_sq && !have_dq && !have_par_begin && 359 !have_cmd) 360 return (WRDE_BADCHAR); 361 need_par_end = false; 362 break; 363 case '(': 364 if (!quote && !dollar && !have_sq && !have_dq && 365 !have_cmd) 366 return (WRDE_BADCHAR); 367 if (dollar) { 368 if (!quote && !have_sq) 369 need_cmd_new = true; 370 have_cmd = true; 371 } 372 break; 373 case ')': 374 if (!quote && !have_sq && !have_dq && !have_cmd) 375 return (WRDE_BADCHAR); 376 need_cmd_new = false; 377 break; 378 case '|': case '&': case ';': case '<': case '>': case '\n': 379 if (!quote && !have_sq && !have_dq && !have_cmd) 380 return (WRDE_BADCHAR); 381 break; 382 default: 383 break; 384 } 385 quote = dollar = false; 386 } 387 if (quote || dollar || need_sq || need_dq || need_par_end || 388 need_cmd_old || need_cmd_new) 389 return (WRDE_SYNTAX); 390 391 return (0); 392 } 393 394 /* 395 * wordfree -- 396 * Free the result of wordexp(). See wordexp(3). 397 * 398 * Specified by IEEE Std. 1003.1-2001. 399 */ 400 void 401 wordfree(wordexp_t *we) 402 { 403 404 if (we == NULL) 405 return; 406 free(we->we_wordv); 407 free(we->we_strings); 408 we->we_wordv = NULL; 409 we->we_strings = NULL; 410 we->we_nbytes = 0; 411 we->we_wordc = 0; 412 } 413