/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2019 Joyent, Inc. */ #include #include #include #include #include #include /* * Safely read a contiguous region of memory from 'addr' in the address space * of a particular process into the supplied kernel buffer (*buf, sz). * Partially mapped regions will result in a partial read terminating at the * first hole in the address space. The number of bytes actually read is * returned to the caller via 'rdsz'. */ int prreadbuf(proc_t *p, uintptr_t ustart, char *buf, size_t sz, size_t *rdsz) { int error = 0; size_t rem = sz; off_t pos = 0; if (rdsz != NULL) *rdsz = 0; while (rem != 0) { uintptr_t addr = ustart + pos; size_t len = MIN(rem, PAGESIZE - (addr & PAGEOFFSET)); if ((error = uread(p, buf + pos, len, addr)) != 0) { if (error == ENXIO) { /* * ENXIO from uread() indicates that the page * does not exist. This will simply be a * partial read. */ error = 0; } break; } rem -= len; pos += len; } if (rdsz != NULL) *rdsz = pos; return (error); } /* * Effectively a truncating version of copyinstr(). * * The resulting string is guaranteed to be truncated to fit within the buffer * (hence sz == 0 is not supported). The returned size includes the truncating * NUL. */ int prreadstr(proc_t *p, uintptr_t ustart, char *buf, size_t bufsz, size_t *rdsz) { size_t slen; int err; VERIFY(bufsz != 0); if ((err = prreadbuf(p, ustart, buf, bufsz, &slen)) != 0) return (err); slen = strnlen(buf, slen); if (slen == bufsz) slen--; buf[slen++] = '\0'; if (rdsz != NULL) *rdsz = slen; return (0); } /* * /proc/pid/cmdline: Linux-compatible '\0'-separated process argv. * * Unlike /proc/pid/argv, this looks at the exec()-time argv string area, rather * than starting from the argv[] array. Thus changes to the array are not * noticed, but direct modifications of the string are visible here. Since it's * common for applications to expect it, we implement the Linux semantics here. * * There is special handling if the process has modified its argv: if the last * byte of the argv string area is no longer NUL, then we presume that it has * done setproctitle() or similar, and we should copy it as a single string from * the start, even though it overflows into the env string area. Note that we * can't use copyinstr() as that returns ENAMETOOLONG rather than truncating as * we need. * * Otherwise, we provide the argv string area in toto. */ int prreadcmdline(proc_t *p, char *buf, size_t bufsz, size_t *slen) { user_t *up = &p->p_user; uint8_t term; int err = 0; VERIFY(bufsz == PRMAXARGVLEN); VERIFY(MUTEX_HELD(&p->p_lock)); if ((p->p_flag & SSYS) || p->p_as == &kas || up->u_argvstrsize == 0) { bcopy(up->u_psargs, buf, MIN(bufsz, sizeof (up->u_psargs))); buf[bufsz - 1] = '\0'; *slen = strlen(buf) + 1; return (0); } VERIFY(up->u_argvstrs != (uintptr_t)NULL); mutex_exit(&p->p_lock); if (uread(p, &term, sizeof (term), up->u_argvstrs + up->u_argvstrsize - 1) != 0) { err = EFAULT; goto out; } if (term != '\0') { err = prreadstr(p, up->u_argvstrs, buf, bufsz, slen); } else { size_t size = MIN(bufsz, up->u_argvstrsize); err = prreadbuf(p, up->u_argvstrs, buf, size, slen); } out: mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); return (err); } /* * Attempt to read the argument vector (argv) from this process. The caller * must hold the p_lock mutex, and have marked the process P_PR_LOCK (e.g. via * prlock or lx_prlock). * * The caller must provide a buffer (buf, buflen). We will concatenate each * argument string (including the NUL terminator) into this buffer. The number * of characters written to this buffer (including the final NUL terminator) * will be stored in 'slen'. */ int prreadargv(proc_t *p, char *buf, size_t bufsz, size_t *slen) { int error; user_t *up; struct as *as; size_t pos = 0; caddr_t *argv = NULL; size_t argvsz = 0; int i; VERIFY(MUTEX_HELD(&p->p_lock)); VERIFY(p->p_proc_flag & P_PR_LOCK); up = PTOU(p); as = p->p_as; if ((p->p_flag & SSYS) || as == &kas || up->u_argv == (uintptr_t)NULL) { /* * Return the regular psargs string to the caller. */ bcopy(up->u_psargs, buf, MIN(bufsz, sizeof (up->u_psargs))); buf[bufsz - 1] = '\0'; *slen = strlen(buf) + 1; return (0); } /* * Allocate space to store argv array. */ argvsz = up->u_argc * (p->p_model == DATAMODEL_ILP32 ? sizeof (caddr32_t) : sizeof (caddr_t)); argv = kmem_alloc(argvsz, KM_SLEEP); /* * Extract the argv array from the target process. Drop p_lock * while we do I/O to avoid deadlock with the clock thread. */ mutex_exit(&p->p_lock); if ((error = prreadbuf(p, up->u_argv, (char *)argv, argvsz, NULL)) != 0) { kmem_free(argv, argvsz); mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); return (-1); } /* * Read each argument string from the pointers in the argv array. */ pos = 0; for (i = 0; i < up->u_argc; i++) { size_t rdsz, trysz; uintptr_t arg; off_t j; boolean_t found_nul; boolean_t do_retry = B_TRUE; #ifdef _SYSCALL32_IMPL if (p->p_model == DATAMODEL_ILP32) { arg = (uintptr_t)((caddr32_t *)argv)[i]; } else { arg = (uintptr_t)argv[i]; } #else arg = (uintptr_t)argv[i]; #endif /* * Stop trying to read arguments if we reach a NULL * pointer in the vector. */ if (arg == (uintptr_t)NULL) break; /* * Stop reading if we have read the maximum length * we can return to the user. */ if (pos >= bufsz) break; /* * Initially we try a short read, on the assumption that * most individual argument strings are less than 80 * characters long. */ if ((trysz = MIN(80, bufsz - pos - 1)) < 80) { /* * We don't have room in the target buffer for even * an entire short read, so there is no need to retry * with a longer read. */ do_retry = B_FALSE; } retry: /* * Read string data for this argument. Leave room * in the buffer for a final NUL terminator. */ if ((error = prreadbuf(p, arg, (char *)&buf[pos], trysz, &rdsz)) != 0) { /* * There was a problem reading this string * from the process. Give up. */ break; } /* * Find the NUL terminator. */ found_nul = B_FALSE; for (j = 0; j < rdsz; j++) { if (buf[pos + j] == '\0') { found_nul = B_TRUE; break; } } if (!found_nul && do_retry) { /* * We did not find a NUL terminator, but this * was a first pass short read. Try once more * with feeling. */ trysz = bufsz - pos - 1; do_retry = B_FALSE; goto retry; } /* * Commit the string we read to the buffer. */ pos += j + 1; if (!found_nul && pos < bufsz) { /* * A NUL terminator was not found; add one. */ buf[pos++] = '\0'; } } /* * Ensure the entire string is NUL-terminated. */ buf[bufsz - 1] = '\0'; mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); kmem_free(argv, argvsz); /* * If the operation was a success, return the copied string length * to the caller. */ *slen = (error == 0) ? pos : 0; return (error); } /* * Similar to prreadargv except reads the env vector. This is slightly more * complex because there is no count for the env vector that corresponds to * u_argc. */ int prreadenvv(proc_t *p, char *buf, size_t bufsz, size_t *slen) { int error; user_t *up; struct as *as; size_t pos = 0; caddr_t *envp = NULL; uintptr_t tmpp = (uintptr_t)NULL; size_t envpsz = 0, rdsz = 0; int i; int cnt, bound; VERIFY(MUTEX_HELD(&p->p_lock)); VERIFY(p->p_proc_flag & P_PR_LOCK); up = PTOU(p); as = p->p_as; if ((p->p_flag & SSYS) || as == &kas || up->u_envp == (uintptr_t)NULL) { /* * Return empty string. */ buf[0] = '\0'; *slen = 1; return (0); } /* * Drop p_lock while we do I/O to avoid deadlock with the clock thread. */ mutex_exit(&p->p_lock); /* * We first have to count how many env entries we have. This is * somewhat painful. We extract the env entries from the target process * one entry at a time. Stop trying to read env entries if we reach a * NULL pointer in the vector or hit our upper bound (which we take * as the bufsz/4) to ensure we don't run off. */ rdsz = (p->p_model == DATAMODEL_ILP32 ? sizeof (caddr32_t) : sizeof (caddr_t)); bound = (int)(bufsz / 4); for (cnt = 0, tmpp = up->u_envp; cnt < bound; cnt++, tmpp += rdsz) { caddr_t tmp = NULL; if ((error = prreadbuf(p, tmpp, (char *)&tmp, rdsz, NULL)) != 0) { mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); return (-1); } if (tmp == NULL) break; } if (cnt == 0) { /* Return empty string. */ buf[0] = '\0'; *slen = 1; mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); return (0); } /* * Allocate space to store env array. */ envpsz = cnt * (p->p_model == DATAMODEL_ILP32 ? sizeof (caddr32_t) : sizeof (caddr_t)); envp = kmem_alloc(envpsz, KM_SLEEP); /* * Extract the env array from the target process. */ if ((error = prreadbuf(p, up->u_envp, (char *)envp, envpsz, NULL)) != 0) { kmem_free(envp, envpsz); mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); return (-1); } /* * Read each env string from the pointers in the env array. */ pos = 0; for (i = 0; i < cnt; i++) { size_t rdsz, trysz; uintptr_t ev; off_t j; boolean_t found_nul; boolean_t do_retry = B_TRUE; #ifdef _SYSCALL32_IMPL if (p->p_model == DATAMODEL_ILP32) { ev = (uintptr_t)((caddr32_t *)envp)[i]; } else { ev = (uintptr_t)envp[i]; } #else ev = (uintptr_t)envp[i]; #endif /* * Stop trying to read env entries if we reach a NULL * pointer in the vector. */ if (ev == (uintptr_t)NULL) break; /* * Stop reading if we have read the maximum length * we can return to the user. */ if (pos >= bufsz) break; /* * Initially we try a short read, on the assumption that * most individual env strings are less than 80 * characters long. */ if ((trysz = MIN(80, bufsz - pos - 1)) < 80) { /* * We don't have room in the target buffer for even * an entire short read, so there is no need to retry * with a longer read. */ do_retry = B_FALSE; } retry: /* * Read string data for this env var. Leave room * in the buffer for a final NUL terminator. */ if ((error = prreadbuf(p, ev, (char *)&buf[pos], trysz, &rdsz)) != 0) { /* * There was a problem reading this string * from the process. Give up. */ break; } /* * Find the NUL terminator. */ found_nul = B_FALSE; for (j = 0; j < rdsz; j++) { if (buf[pos + j] == '\0') { found_nul = B_TRUE; break; } } if (!found_nul && do_retry) { /* * We did not find a NUL terminator, but this * was a first pass short read. Try once more * with feeling. */ trysz = bufsz - pos - 1; do_retry = B_FALSE; goto retry; } /* * Commit the string we read to the buffer. */ pos += j + 1; if (!found_nul && pos < bufsz) { /* * A NUL terminator was not found; add one. */ buf[pos++] = '\0'; } } /* * Ensure the entire string is NUL-terminated. */ buf[bufsz - 1] = '\0'; mutex_enter(&p->p_lock); VERIFY(p->p_proc_flag & P_PR_LOCK); kmem_free(envp, envpsz); /* * If the operation was a success, return the copied string length * to the caller. */ *slen = (error == 0) ? pos : 0; return (error); }