#include <sys/types.h>
#include <sys/param.h>
#include <sys/thread.h>
#include <sys/sysmacros.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/vnode.h>
#include <sys/mman.h>
#include <sys/kmem.h>
#include <sys/proc.h>
#include <sys/pathname.h>
#include <sys/policy.h>
#include <sys/cmn_err.h>
#include <sys/systm.h>
#include <sys/elf.h>
#include <sys/vmsystm.h>
#include <sys/debug.h>
#include <sys/auxv.h>
#include <sys/exec.h>
#include <sys/prsystm.h>
#include <vm/as.h>
#include <vm/rm.h>
#include <vm/seg.h>
#include <vm/seg_vn.h>
#include <sys/modctl.h>
#include <sys/systeminfo.h>
#include <sys/vmparam.h>
#include <sys/machelf.h>
#include <sys/shm_impl.h>
#include <sys/archsystm.h>
#include <sys/fasttrap.h>
#include <sys/brand.h>
#include "elf_impl.h"
#include <sys/sdt.h>
#include <sys/siginfo.h>
#include <sys/random.h>
#include <core_shstrtab.h>
#if defined(__x86)
#include <sys/comm_page_util.h>
#include <sys/fp.h>
#endif
extern int at_flags;
extern volatile size_t aslr_max_brk_skew;
#define ORIGIN_STR "ORIGIN"
#define ORIGIN_STR_SIZE 6
static int getelfhead(vnode_t *, cred_t *, Ehdr *, uint_t *, uint_t *,
uint_t *);
static int getelfphdr(vnode_t *, cred_t *, const Ehdr *, uint_t, caddr_t *,
size_t *);
static int getelfshdr(vnode_t *, cred_t *, const Ehdr *, uint_t, uint_t,
caddr_t *, size_t *, caddr_t *, size_t *);
static size_t elfsize(const Ehdr *, uint_t, const caddr_t, uintptr_t *);
static int mapelfexec(vnode_t *, Ehdr *, uint_t, caddr_t, Phdr **, Phdr **,
Phdr **, Phdr **, Phdr *, caddr_t *, caddr_t *, intptr_t *, uintptr_t *,
size_t, size_t *, size_t *);
#ifdef _ELF32_COMPAT
extern size_t elf_datasz_max;
extern size_t elf_zeropg_sz;
extern void elf_ctx_resize_scratch(elf_core_ctx_t *, size_t);
extern uint_t elf_nphdr_max;
extern uint_t elf_nshdr_max;
extern size_t elf_shstrtab_max;
#else
size_t elf_datasz_max = 1 * 1024 * 1024;
size_t elf_zeropg_sz = 4 * 1024;
uint_t elf_nphdr_max = 1000;
uint_t elf_nshdr_max = 10000;
size_t elf_shstrtab_max = 100 * 1024;
#endif
static int
dtrace_safe_phdr(Phdr *phdrp, struct uarg *args, uintptr_t base)
{
ASSERT(phdrp->p_type == PT_SUNWDTRACE);
if (phdrp->p_memsz < PT_SUNWDTRACE_SIZE ||
(phdrp->p_flags & (PF_R | PF_W | PF_X)) != (PF_R | PF_W | PF_X))
return (-1);
args->thrptr = phdrp->p_vaddr + base;
return (0);
}
static int
handle_secflag_dt(proc_t *p, uint_t dt, uint_t val)
{
uint_t flag;
switch (dt) {
case DT_SUNW_ASLR:
flag = PROC_SEC_ASLR;
break;
default:
return (EINVAL);
}
if (val == 0) {
if (secflag_isset(p->p_secflags.psf_lower, flag))
return (EPERM);
if ((secpolicy_psecflags(CRED(), p, p) != 0) &&
secflag_isset(p->p_secflags.psf_inherit, flag))
return (EPERM);
secflag_clear(&p->p_secflags.psf_effective, flag);
} else {
if (!secflag_isset(p->p_secflags.psf_upper, flag))
return (EPERM);
if ((secpolicy_psecflags(CRED(), p, p) != 0) &&
!secflag_isset(p->p_secflags.psf_inherit, flag))
return (EPERM);
secflag_set(&p->p_secflags.psf_effective, flag);
}
return (0);
}
#ifndef _ELF32_COMPAT
void
elf_ctx_resize_scratch(elf_core_ctx_t *ctx, size_t sz)
{
size_t target = MIN(sz, elf_datasz_max);
if (target > ctx->ecc_bufsz) {
if (ctx->ecc_buf != NULL) {
kmem_free(ctx->ecc_buf, ctx->ecc_bufsz);
}
ctx->ecc_buf = kmem_alloc(target, KM_SLEEP);
ctx->ecc_bufsz = target;
}
}
#endif
int
mapexec_brand(vnode_t *vp, uarg_t *args, Ehdr *ehdr, Addr *uphdr_vaddr,
intptr_t *voffset, caddr_t exec_file, int *interp, caddr_t *bssbase,
caddr_t *brkbase, size_t *brksize, uintptr_t *lddatap)
{
size_t len, phdrsize;
struct vattr vat;
caddr_t phdrbase = NULL;
uint_t nshdrs, shstrndx, nphdrs;
int error = 0;
Phdr *uphdr = NULL;
Phdr *junk = NULL;
Phdr *dynphdr = NULL;
Phdr *dtrphdr = NULL;
uintptr_t lddata, minaddr;
size_t execsz;
if (lddatap != NULL)
*lddatap = 0;
if (error = execpermissions(vp, &vat, args)) {
uprintf("%s: Cannot execute %s\n", exec_file, args->pathname);
return (error);
}
if ((error = getelfhead(vp, CRED(), ehdr, &nshdrs, &shstrndx,
&nphdrs)) != 0 ||
(error = getelfphdr(vp, CRED(), ehdr, nphdrs, &phdrbase,
&phdrsize)) != 0) {
uprintf("%s: Cannot read %s\n", exec_file, args->pathname);
return (error);
}
if ((len = elfsize(ehdr, nphdrs, phdrbase, &lddata)) == 0) {
uprintf("%s: Nothing to load in %s", exec_file, args->pathname);
kmem_free(phdrbase, phdrsize);
return (ENOEXEC);
}
if (lddatap != NULL)
*lddatap = lddata;
if (error = mapelfexec(vp, ehdr, nphdrs, phdrbase, &uphdr, &dynphdr,
&junk, &dtrphdr, NULL, bssbase, brkbase, voffset, &minaddr,
len, &execsz, brksize)) {
uprintf("%s: Cannot map %s\n", exec_file, args->pathname);
if (uphdr != NULL && uphdr->p_flags == 0)
kmem_free(uphdr, sizeof (Phdr));
kmem_free(phdrbase, phdrsize);
return (error);
}
*interp = (dynphdr == NULL) ? 0 : 1;
if (ehdr->e_type == ET_EXEC && *interp == 0)
*voffset = minaddr;
if (uphdr != NULL) {
*uphdr_vaddr = uphdr->p_vaddr;
if (uphdr->p_flags == 0)
kmem_free(uphdr, sizeof (Phdr));
} else {
*uphdr_vaddr = (Addr)-1;
}
kmem_free(phdrbase, phdrsize);
return (error);
}
int
elfexec(vnode_t *vp, execa_t *uap, uarg_t *args, intpdata_t *idatap,
int level, size_t *execsz, int setid, caddr_t exec_file, cred_t *cred,
int brand_action)
{
caddr_t phdrbase = NULL;
caddr_t bssbase = 0;
caddr_t brkbase = 0;
size_t brksize = 0;
size_t dlnsize;
aux_entry_t *aux;
int error;
ssize_t resid;
int fd = -1;
intptr_t voffset;
Phdr *intphdr = NULL;
Phdr *dynamicphdr = NULL;
Phdr *stphdr = NULL;
Phdr *uphdr = NULL;
Phdr *junk = NULL;
size_t len;
size_t postfixsize = 0;
size_t i;
Phdr *phdrp;
Phdr *dataphdrp = NULL;
Phdr *dtrphdr;
Phdr *capphdr = NULL;
Cap *cap = NULL;
size_t capsize;
int hasu = 0;
int hasauxv = 0;
int hasintp = 0;
int branded = 0;
boolean_t dynuphdr = B_FALSE;
struct proc *p = ttoproc(curthread);
struct user *up = PTOU(p);
struct bigwad {
Ehdr ehdr;
aux_entry_t elfargs[__KERN_NAUXV_IMPL];
char dl_name[MAXPATHLEN];
char pathbuf[MAXPATHLEN];
struct vattr vattr;
struct execenv exenv;
} *bigwad;
Ehdr *ehdrp;
uint_t nshdrs, shstrndx, nphdrs;
size_t phdrsize;
char *dlnp;
char *pathbufp;
rlim64_t limit;
rlim64_t roundlimit;
ASSERT(p->p_model == DATAMODEL_ILP32 || p->p_model == DATAMODEL_LP64);
bigwad = kmem_alloc(sizeof (struct bigwad), KM_SLEEP);
ehdrp = &bigwad->ehdr;
dlnp = bigwad->dl_name;
pathbufp = bigwad->pathbuf;
if ((error = getelfhead(vp, CRED(), ehdrp, &nshdrs, &shstrndx,
&nphdrs)) != 0 ||
(error = getelfphdr(vp, CRED(), ehdrp, nphdrs, &phdrbase,
&phdrsize)) != 0)
goto out;
if (ehdrp->e_entry == 0) {
uprintf("%s: Bad entry point\n", exec_file);
goto bad;
}
#ifdef _LP64
if (ehdrp->e_ident[EI_CLASS] == ELFCLASS32) {
args->to_model = DATAMODEL_ILP32;
*execsz = btopr(SINCR) + btopr(SSIZE) + btopr(NCARGS32-1);
} else {
args->to_model = DATAMODEL_LP64;
args->stk_prot &= ~PROT_EXEC;
#if defined(__x86)
args->dat_prot &= ~PROT_EXEC;
#endif
*execsz = btopr(SINCR) + btopr(SSIZE) + btopr(NCARGS64-1);
}
#else
args->to_model = DATAMODEL_ILP32;
*execsz = btopr(SINCR) + btopr(SSIZE) + btopr(NCARGS-1);
#endif
if ((level <= INTP_MAXDEPTH) &&
(brand_action != EBA_NATIVE) && (PROC_IS_BRANDED(p))) {
error = BROP(p)->b_elfexec(vp, uap, args,
idatap, level + 1, execsz, setid, exec_file, cred,
brand_action);
goto out;
}
const uint_t hsize = ehdrp->e_phentsize;
phdrp = (Phdr *)phdrbase;
for (i = nphdrs; i > 0; i--) {
switch (phdrp->p_type) {
case PT_INTERP:
hasauxv = hasintp = 1;
break;
case PT_PHDR:
hasu = 1;
break;
case PT_SUNWSTACK:
args->stk_prot = PROT_USER;
if (phdrp->p_flags & PF_R)
args->stk_prot |= PROT_READ;
if (phdrp->p_flags & PF_W)
args->stk_prot |= PROT_WRITE;
if (phdrp->p_flags & PF_X)
args->stk_prot |= PROT_EXEC;
break;
case PT_LOAD:
dataphdrp = phdrp;
break;
case PT_SUNWCAP:
capphdr = phdrp;
break;
case PT_DYNAMIC:
dynamicphdr = phdrp;
break;
}
phdrp = (Phdr *)((caddr_t)phdrp + hsize);
}
if (ehdrp->e_type != ET_EXEC) {
dataphdrp = NULL;
hasauxv = 1;
}
if (dataphdrp != NULL) {
args->dat_prot = PROT_USER;
if (dataphdrp->p_flags & PF_R)
args->dat_prot |= PROT_READ;
if (dataphdrp->p_flags & PF_W)
args->dat_prot |= PROT_WRITE;
if (dataphdrp->p_flags & PF_X)
args->dat_prot |= PROT_EXEC;
}
if (hasauxv) {
if (hasintp && hasu) {
args->auxsize = (10 + 5) * sizeof (aux_entry_t);
} else if (hasintp) {
args->auxsize = (10 + 2) * sizeof (aux_entry_t);
} else {
args->auxsize = 10 * sizeof (aux_entry_t);
}
} else {
args->auxsize = 0;
}
if (args->emulator != NULL)
args->auxsize += sizeof (aux_entry_t);
#if defined(__amd64)
args->auxsize += 3 * sizeof (aux_entry_t);
#endif
if ((brand_action != EBA_NATIVE) && (PROC_IS_BRANDED(p))) {
branded = 1;
args->auxsize += 4 * sizeof (aux_entry_t);
}
if ((dynamicphdr != NULL) && (dynamicphdr->p_filesz > 0)) {
const size_t dynfilesz = dynamicphdr->p_filesz;
const size_t dynoffset = dynamicphdr->p_offset;
Dyn *dyn, *dp;
if (dynoffset > MAXOFFSET_T ||
dynfilesz > MAXOFFSET_T ||
dynoffset + dynfilesz > MAXOFFSET_T) {
uprintf("%s: cannot read full .dynamic section\n",
exec_file);
error = EINVAL;
goto out;
}
#define DYN_STRIDE 100
for (i = 0; i < dynfilesz; i += sizeof (*dyn) * DYN_STRIDE) {
const size_t remdyns = (dynfilesz - i) / sizeof (*dyn);
const size_t ndyns = MIN(DYN_STRIDE, remdyns);
const size_t dynsize = ndyns * sizeof (*dyn);
dyn = kmem_alloc(dynsize, KM_SLEEP);
if ((error = vn_rdwr(UIO_READ, vp, (caddr_t)dyn,
(ssize_t)dynsize, (offset_t)(dynoffset + i),
UIO_SYSSPACE, 0, (rlim64_t)0,
CRED(), NULL)) != 0) {
uprintf("%s: cannot read .dynamic section\n",
exec_file);
goto out;
}
for (dp = dyn; dp < (dyn + ndyns); dp++) {
if (dp->d_tag == DT_SUNW_ASLR) {
if ((error = handle_secflag_dt(p,
DT_SUNW_ASLR,
dp->d_un.d_val)) != 0) {
uprintf("%s: error setting "
"security-flag from "
"DT_SUNW_ASLR: %d\n",
exec_file, error);
goto out;
}
}
}
kmem_free(dyn, dynsize);
}
}
if (capphdr != NULL &&
(capsize = capphdr->p_filesz) > 0 &&
capsize <= 16 * sizeof (*cap)) {
const uint_t ncaps = capsize / sizeof (*cap);
Cap *cp;
cap = kmem_alloc(capsize, KM_SLEEP);
if ((error = vn_rdwr(UIO_READ, vp, (caddr_t)cap,
(ssize_t)capsize, (offset_t)capphdr->p_offset,
UIO_SYSSPACE, 0, (rlim64_t)0, CRED(), NULL)) != 0) {
uprintf("%s: Cannot read capabilities section\n",
exec_file);
goto out;
}
for (cp = cap; cp < cap + ncaps; cp++) {
if (cp->c_tag == CA_SUNW_SF_1 &&
(cp->c_un.c_val & SF1_SUNW_ADDR32)) {
if (args->to_model == DATAMODEL_LP64)
args->addr32 = 1;
break;
}
}
}
aux = bigwad->elfargs;
if ((error = exec_args(uap, args, idatap, (void **)&aux)) != 0) {
if (error == -1) {
error = ENOEXEC;
goto bad;
}
goto out;
}
if (ehdrp->e_type == ET_DYN)
len = elfsize(ehdrp, nphdrs, phdrbase, NULL);
else
len = 0;
dtrphdr = NULL;
error = mapelfexec(vp, ehdrp, nphdrs, phdrbase, &uphdr, &intphdr,
&stphdr, &dtrphdr, dataphdrp, &bssbase, &brkbase, &voffset, NULL,
len, execsz, &brksize);
dynuphdr = (uphdr != NULL && uphdr->p_flags == 0);
if (error != 0)
goto bad;
if (uphdr != NULL && intphdr == NULL)
goto bad;
if (dtrphdr != NULL && dtrace_safe_phdr(dtrphdr, args, voffset) != 0) {
uprintf("%s: Bad DTrace phdr in %s\n", exec_file, exec_file);
goto bad;
}
if (intphdr != NULL) {
size_t len;
uintptr_t lddata;
char *p;
struct vnode *nvp;
dlnsize = intphdr->p_filesz;
if (intphdr->p_filesz > MAXPATHLEN || dlnsize > MAXPATHLEN ||
dlnsize == 0 || dlnsize < intphdr->p_filesz) {
goto bad;
}
if ((error = vn_rdwr(UIO_READ, vp, dlnp,
(ssize_t)intphdr->p_filesz, (offset_t)intphdr->p_offset,
UIO_SYSSPACE, 0, (rlim64_t)0, CRED(), &resid)) != 0) {
uprintf("%s: Cannot obtain interpreter pathname\n",
exec_file);
goto bad;
}
if (resid != 0 || dlnp[dlnsize - 1] != '\0')
goto bad;
for (p = dlnp; p = strchr(p, '$'); ) {
uint_t len, curlen;
char *_ptr;
if (strncmp(++p, ORIGIN_STR, ORIGIN_STR_SIZE))
continue;
if ((setid & EXECSETID_SETID) != 0) {
error = ENOEXEC;
goto bad;
}
curlen = 0;
len = p - dlnp - 1;
if (len) {
bcopy(dlnp, pathbufp, len);
curlen += len;
}
if (_ptr = strrchr(args->pathname, '/')) {
len = _ptr - args->pathname;
if ((curlen + len) > MAXPATHLEN)
break;
bcopy(args->pathname, &pathbufp[curlen], len);
curlen += len;
} else {
pathbufp[curlen] = '.';
curlen++;
}
p += ORIGIN_STR_SIZE;
len = strlen(p);
if ((curlen + len) > MAXPATHLEN)
break;
bcopy(p, &pathbufp[curlen], len);
curlen += len;
pathbufp[curlen++] = '\0';
bcopy(pathbufp, dlnp, curlen);
}
if (strcmp(dlnp, USR_LIB_RTLD) == 0)
dlnp += 4;
error = lookupname(dlnp, UIO_SYSSPACE, FOLLOW, NULLVPP, &nvp);
if (error && dlnp != bigwad->dl_name) {
error = lookupname(dlnp -= 4, UIO_SYSSPACE, FOLLOW,
NULLVPP, &nvp);
}
if (error) {
uprintf("%s: Cannot find %s\n", exec_file, dlnp);
goto bad;
}
if (uphdr) {
if (ehdrp->e_type == ET_DYN) {
bigwad->exenv.ex_brkbase = (caddr_t)PAGESIZE;
bigwad->exenv.ex_bssbase = (caddr_t)PAGESIZE;
} else {
bigwad->exenv.ex_bssbase = bssbase;
bigwad->exenv.ex_brkbase = brkbase;
}
bigwad->exenv.ex_brksize = brksize;
bigwad->exenv.ex_magic = elfmagic;
bigwad->exenv.ex_vp = vp;
setexecenv(&bigwad->exenv);
ADDAUX(aux, AT_PHDR, uphdr->p_vaddr + voffset)
ADDAUX(aux, AT_PHENT, ehdrp->e_phentsize)
ADDAUX(aux, AT_PHNUM, nphdrs)
ADDAUX(aux, AT_ENTRY, ehdrp->e_entry + voffset)
} else {
if ((error = execopen(&vp, &fd)) != 0) {
VN_RELE(nvp);
goto bad;
}
ADDAUX(aux, AT_EXECFD, fd)
}
if ((error = execpermissions(nvp, &bigwad->vattr, args)) != 0) {
VN_RELE(nvp);
uprintf("%s: Cannot execute %s\n", exec_file, dlnp);
goto bad;
}
kmem_free(phdrbase, phdrsize);
phdrbase = NULL;
if ((error = getelfhead(nvp, CRED(), ehdrp, &nshdrs,
&shstrndx, &nphdrs)) != 0 ||
(error = getelfphdr(nvp, CRED(), ehdrp, nphdrs, &phdrbase,
&phdrsize)) != 0) {
VN_RELE(nvp);
uprintf("%s: Cannot read %s\n", exec_file, dlnp);
goto bad;
}
if ((len = elfsize(ehdrp, nphdrs, phdrbase, &lddata)) == 0) {
VN_RELE(nvp);
uprintf("%s: Nothing to load in %s\n", exec_file, dlnp);
goto bad;
}
dtrphdr = NULL;
error = mapelfexec(nvp, ehdrp, nphdrs, phdrbase, NULL, &junk,
&junk, &dtrphdr, NULL, NULL, NULL, &voffset, NULL, len,
execsz, NULL);
if (error || junk != NULL) {
VN_RELE(nvp);
uprintf("%s: Cannot map %s\n", exec_file, dlnp);
goto bad;
}
if (dtrphdr != NULL &&
dtrace_safe_phdr(dtrphdr, args, voffset) != 0) {
VN_RELE(nvp);
uprintf("%s: Bad DTrace phdr in %s\n", exec_file, dlnp);
goto bad;
}
VN_RELE(nvp);
ADDAUX(aux, AT_SUN_LDDATA, voffset + lddata)
}
if (hasauxv) {
int auxf = AF_SUN_HWCAPVERIFY;
#if defined(__amd64)
size_t fpsize;
int fptype;
#endif
ADDAUX(aux, AT_BASE, voffset)
ADDAUX(aux, AT_FLAGS, at_flags)
ADDAUX(aux, AT_PAGESZ, PAGESIZE)
if ((setid &= ~EXECSETID_SETID) != 0)
auxf |= AF_SUN_SETUGID;
if ((brand_action == EBA_NATIVE) && (PROC_IS_BRANDED(p) &&
(setid &= ~EXECSETID_SETID) != 0))
auxf &= ~AF_SUN_SETUGID;
args->auxp_auxflags =
(char *)((char *)args->stackend +
((char *)&aux->a_type -
(char *)bigwad->elfargs));
ADDAUX(aux, AT_SUN_AUXFLAGS, auxf);
if (args->to_model == DATAMODEL_NATIVE) {
ADDAUX(aux, AT_SUN_HWCAP, auxv_hwcap)
ADDAUX(aux, AT_SUN_HWCAP2, auxv_hwcap_2)
ADDAUX(aux, AT_SUN_HWCAP3, auxv_hwcap_3)
} else {
ADDAUX(aux, AT_SUN_HWCAP, auxv_hwcap32)
ADDAUX(aux, AT_SUN_HWCAP2, auxv_hwcap32_2)
ADDAUX(aux, AT_SUN_HWCAP3, auxv_hwcap32_3)
}
if (branded) {
args->auxp_brand =
(char *)((char *)args->stackend +
((char *)&aux->a_type -
(char *)bigwad->elfargs));
ADDAUX(aux, AT_SUN_BRAND_AUX1, 0)
ADDAUX(aux, AT_SUN_BRAND_AUX2, 0)
ADDAUX(aux, AT_SUN_BRAND_AUX3, 0)
}
#if defined(__amd64)
if (args->commpage != (uintptr_t)NULL ||
(args->commpage = (uintptr_t)comm_page_mapin()) !=
(uintptr_t)NULL) {
ADDAUX(aux, AT_SUN_COMMPAGE, args->commpage)
} else {
ADDAUX(aux, AT_NULL, 0)
}
fptype = AT_386_FPINFO_NONE;
fpu_auxv_info(&fptype, &fpsize);
if (fptype != AT_386_FPINFO_NONE) {
ADDAUX(aux, AT_SUN_FPTYPE, fptype)
ADDAUX(aux, AT_SUN_FPSIZE, fpsize)
} else {
ADDAUX(aux, AT_NULL, 0)
ADDAUX(aux, AT_NULL, 0)
}
#endif
ADDAUX(aux, AT_NULL, 0)
postfixsize = (uintptr_t)aux - (uintptr_t)bigwad->elfargs;
if (postfixsize != args->auxsize) {
DTRACE_PROBE2(elfexec_badaux, size_t, postfixsize,
size_t, args->auxsize);
goto bad;
}
ASSERT(postfixsize <= __KERN_NAUXV_IMPL * sizeof (aux_entry_t));
}
limit = btop(p->p_vmem_ctl);
roundlimit = btopr(p->p_vmem_ctl);
if ((roundlimit > limit && *execsz > roundlimit) ||
(roundlimit < limit && *execsz > limit)) {
mutex_enter(&p->p_lock);
(void) rctl_action(rctlproc_legacy[RLIMIT_VMEM], p->p_rctls, p,
RCA_SAFE);
mutex_exit(&p->p_lock);
error = ENOMEM;
goto bad;
}
bzero(up->u_auxv, sizeof (up->u_auxv));
up->u_commpagep = args->commpage;
if (postfixsize) {
size_t num_auxv;
error = execpoststack(args, bigwad->elfargs, postfixsize);
if (error)
goto bad;
num_auxv = postfixsize / sizeof (aux_entry_t);
ASSERT(num_auxv <= sizeof (up->u_auxv) / sizeof (auxv_t));
aux = bigwad->elfargs;
for (i = 0; i < num_auxv; i++) {
up->u_auxv[i].a_type = aux[i].a_type;
up->u_auxv[i].a_un.a_val = (aux_val_t)aux[i].a_un.a_val;
}
}
args->entry = (uintptr_t)(ehdrp->e_entry + voffset);
if (!uphdr) {
if (ehdrp->e_type == ET_DYN) {
bigwad->exenv.ex_brkbase = (caddr_t)0;
bigwad->exenv.ex_bssbase = (caddr_t)0;
bigwad->exenv.ex_brksize = 0;
} else {
bigwad->exenv.ex_brkbase = brkbase;
bigwad->exenv.ex_bssbase = bssbase;
bigwad->exenv.ex_brksize = brksize;
}
bigwad->exenv.ex_magic = elfmagic;
bigwad->exenv.ex_vp = vp;
setexecenv(&bigwad->exenv);
}
ASSERT(error == 0);
goto out;
bad:
if (fd != -1)
(void) execclose(fd);
psignal(p, SIGKILL);
if (error == 0)
error = ENOEXEC;
out:
if (dynuphdr)
kmem_free(uphdr, sizeof (Phdr));
if (phdrbase != NULL)
kmem_free(phdrbase, phdrsize);
if (cap != NULL)
kmem_free(cap, capsize);
kmem_free(bigwad, sizeof (struct bigwad));
return (error);
}
static size_t
elfsize(const Ehdr *ehdrp, uint_t nphdrs, const caddr_t phdrbase,
uintptr_t *lddata)
{
const Phdr *phdrp = (Phdr *)phdrbase;
const uint_t hsize = ehdrp->e_phentsize;
boolean_t dfirst = B_TRUE;
uintptr_t loaddr = UINTPTR_MAX;
uintptr_t hiaddr = 0;
uint_t i;
for (i = nphdrs; i > 0; i--) {
if (phdrp->p_type == PT_LOAD) {
const uintptr_t lo = phdrp->p_vaddr;
const uintptr_t hi = lo + phdrp->p_memsz;
loaddr = MIN(lo, loaddr);
hiaddr = MAX(hi, hiaddr);
if ((lddata != NULL) && dfirst &&
(phdrp->p_flags & PF_W)) {
*lddata = lo;
dfirst = B_FALSE;
}
}
phdrp = (Phdr *)((caddr_t)phdrp + hsize);
}
if (hiaddr <= loaddr) {
return (0);
}
return (roundup(hiaddr - (loaddr & PAGEMASK), PAGESIZE));
}
static int
getelfhead(vnode_t *vp, cred_t *credp, Ehdr *ehdr, uint_t *nshdrs,
uint_t *shstrndx, uint_t *nphdrs)
{
int error;
ssize_t resid;
if ((error = vn_rdwr(UIO_READ, vp, (caddr_t)ehdr,
sizeof (Ehdr), (offset_t)0, UIO_SYSSPACE, 0,
(rlim64_t)0, credp, &resid)) != 0)
return (error);
if (resid != 0 ||
ehdr->e_ident[EI_MAG2] != ELFMAG2 ||
ehdr->e_ident[EI_MAG3] != ELFMAG3)
return (ENOEXEC);
if ((ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN) ||
#if defined(_ILP32) || defined(_ELF32_COMPAT)
ehdr->e_ident[EI_CLASS] != ELFCLASS32 ||
#else
ehdr->e_ident[EI_CLASS] != ELFCLASS64 ||
#endif
!elfheadcheck(ehdr->e_ident[EI_DATA], ehdr->e_machine,
ehdr->e_flags))
return (EINVAL);
*nshdrs = ehdr->e_shnum;
*shstrndx = ehdr->e_shstrndx;
*nphdrs = ehdr->e_phnum;
if ((*nshdrs == 0 && ehdr->e_shoff != 0) ||
*shstrndx == SHN_XINDEX || *nphdrs == PN_XNUM) {
Shdr shdr;
if (ehdr->e_shoff == 0)
return (EINVAL);
if ((error = vn_rdwr(UIO_READ, vp, (caddr_t)&shdr,
sizeof (shdr), (offset_t)ehdr->e_shoff, UIO_SYSSPACE, 0,
(rlim64_t)0, credp, NULL)) != 0) {
return (error);
}
if (*nshdrs == 0)
*nshdrs = shdr.sh_size;
if (*shstrndx == SHN_XINDEX)
*shstrndx = shdr.sh_link;
if (*nphdrs == PN_XNUM && shdr.sh_info != 0)
*nphdrs = shdr.sh_info;
}
return (0);
}
#if !defined(_LP64) || defined(_ELF32_COMPAT)
#define MINPHENTSZ (offsetof(Phdr, p_flags) + \
sizeof (((Phdr *)NULL)->p_flags))
#else
#define MINPHENTSZ (offsetof(Phdr, p_memsz) + \
sizeof (((Phdr *)NULL)->p_memsz))
#endif
static int
getelfphdr(vnode_t *vp, cred_t *credp, const Ehdr *ehdr, uint_t nphdrs,
caddr_t *phbasep, size_t *phsizep)
{
int err;
if (ehdr->e_phentsize < MINPHENTSZ || (ehdr->e_phentsize & 3))
return (EINVAL);
*phsizep = nphdrs * ehdr->e_phentsize;
if (*phsizep > sizeof (Phdr) * elf_nphdr_max) {
if ((*phbasep = kmem_alloc(*phsizep, KM_NOSLEEP)) == NULL)
return (ENOMEM);
} else {
*phbasep = kmem_alloc(*phsizep, KM_SLEEP);
}
if ((err = vn_rdwr(UIO_READ, vp, *phbasep, (ssize_t)*phsizep,
(offset_t)ehdr->e_phoff, UIO_SYSSPACE, 0, (rlim64_t)0,
credp, NULL)) != 0) {
kmem_free(*phbasep, *phsizep);
*phbasep = NULL;
return (err);
}
return (0);
}
#define MINSHDRSZ (offsetof(Shdr, sh_entsize) + \
sizeof (((Shdr *)NULL)->sh_entsize))
static int
getelfshdr(vnode_t *vp, cred_t *credp, const Ehdr *ehdr, uint_t nshdrs,
uint_t shstrndx, caddr_t *shbasep, size_t *shsizep, char **shstrbasep,
size_t *shstrsizep)
{
int err;
Shdr *shdr;
if (ehdr->e_shentsize < MINSHDRSZ || (ehdr->e_shentsize & 3) ||
nshdrs == 0 || shstrndx >= nshdrs) {
return (EINVAL);
}
*shsizep = nshdrs * ehdr->e_shentsize;
if (*shsizep > sizeof (Shdr) * elf_nshdr_max) {
if ((*shbasep = kmem_alloc(*shsizep, KM_NOSLEEP)) == NULL)
return (ENOMEM);
} else {
*shbasep = kmem_alloc(*shsizep, KM_SLEEP);
}
if ((err = vn_rdwr(UIO_READ, vp, *shbasep, (ssize_t)*shsizep,
(offset_t)ehdr->e_shoff, UIO_SYSSPACE, 0, (rlim64_t)0,
credp, NULL)) != 0) {
kmem_free(*shbasep, *shsizep);
return (err);
}
shdr = (Shdr *)(*shbasep + shstrndx * ehdr->e_shentsize);
if ((*shstrsizep = shdr->sh_size) == 0) {
kmem_free(*shbasep, *shsizep);
return (EINVAL);
}
if (*shstrsizep > elf_shstrtab_max) {
if ((*shstrbasep = kmem_alloc(*shstrsizep,
KM_NOSLEEP)) == NULL) {
kmem_free(*shbasep, *shsizep);
return (ENOMEM);
}
} else {
*shstrbasep = kmem_alloc(*shstrsizep, KM_SLEEP);
}
if ((err = vn_rdwr(UIO_READ, vp, *shstrbasep, (ssize_t)*shstrsizep,
(offset_t)shdr->sh_offset, UIO_SYSSPACE, 0, (rlim64_t)0,
credp, NULL)) != 0) {
kmem_free(*shbasep, *shsizep);
kmem_free(*shstrbasep, *shstrsizep);
return (err);
}
(*shstrbasep)[*shstrsizep - 1] = '\0';
return (0);
}
int
elfreadhdr(vnode_t *vp, cred_t *credp, Ehdr *ehdrp, uint_t *nphdrs,
caddr_t *phbasep, size_t *phsizep)
{
int error;
uint_t nshdrs, shstrndx;
if ((error = getelfhead(vp, credp, ehdrp, &nshdrs, &shstrndx,
nphdrs)) != 0 ||
(error = getelfphdr(vp, credp, ehdrp, *nphdrs, phbasep,
phsizep)) != 0) {
return (error);
}
return (0);
}
static int
mapelfexec(
vnode_t *vp,
Ehdr *ehdr,
uint_t nphdrs,
caddr_t phdrbase,
Phdr **uphdr,
Phdr **intphdr,
Phdr **stphdr,
Phdr **dtphdr,
Phdr *dataphdrp,
caddr_t *bssbase,
caddr_t *brkbase,
intptr_t *voffset,
uintptr_t *minaddrp,
size_t len,
size_t *execsz,
size_t *brksize)
{
Phdr *phdr;
int error, page, prot;
caddr_t addr = NULL;
caddr_t minaddr = (caddr_t)UINTPTR_MAX;
uint_t i;
size_t zfodsz, memsz;
boolean_t ptload = B_FALSE;
off_t offset;
const uint_t hsize = ehdr->e_phentsize;
extern int use_brk_lpg;
if (ehdr->e_type == ET_DYN) {
secflagset_t flags = 0;
if (secflag_enabled(curproc, PROC_SEC_ASLR))
flags |= _MAP_RANDOMIZE;
map_addr(&addr, len, (offset_t)0, 1, flags);
if (addr == NULL)
return (ENOMEM);
*voffset = (intptr_t)addr;
phdr = (Phdr *)phdrbase;
for (i = nphdrs; i > 0; i--) {
if (phdr->p_type == PT_LOAD) {
*voffset -= (uintptr_t)phdr->p_vaddr;
break;
}
phdr = (Phdr *)((caddr_t)phdr + hsize);
}
} else {
*voffset = 0;
}
phdr = (Phdr *)phdrbase;
for (i = nphdrs; i > 0; i--) {
switch (phdr->p_type) {
case PT_LOAD:
ptload = B_TRUE;
prot = PROT_USER;
if (phdr->p_flags & PF_R)
prot |= PROT_READ;
if (phdr->p_flags & PF_W)
prot |= PROT_WRITE;
if (phdr->p_flags & PF_X)
prot |= PROT_EXEC;
addr = (caddr_t)((uintptr_t)phdr->p_vaddr + *voffset);
if (*intphdr != NULL && uphdr != NULL &&
*uphdr == NULL) {
Phdr *cons;
cons = kmem_zalloc(sizeof (Phdr), KM_SLEEP);
cons->p_flags = 0;
cons->p_type = PT_PHDR;
cons->p_vaddr = ((uintptr_t)addr -
phdr->p_offset) + ehdr->e_phoff;
*uphdr = cons;
}
if (phdr->p_filesz > phdr->p_memsz) {
error = EINVAL;
goto bad;
}
if (addr < minaddr)
minaddr = addr;
zfodsz = (size_t)phdr->p_memsz - phdr->p_filesz;
offset = phdr->p_offset;
if (((uintptr_t)offset & PAGEOFFSET) ==
((uintptr_t)addr & PAGEOFFSET) &&
(!(vp->v_flag & VNOMAP))) {
page = 1;
} else {
page = 0;
}
if (brksize != NULL && use_brk_lpg &&
zfodsz != 0 && phdr == dataphdrp &&
(prot & PROT_WRITE)) {
const size_t tlen = P2NPHASE((uintptr_t)addr +
phdr->p_filesz, PAGESIZE);
if (zfodsz > tlen) {
const caddr_t taddr = addr +
phdr->p_filesz + tlen;
curproc->p_brkpageszc =
page_szc(map_pgsz(MAPPGSZ_HEAP,
curproc, taddr, zfodsz - tlen, 0));
}
}
if (curproc->p_brkpageszc != 0 && phdr == dataphdrp &&
(prot & PROT_WRITE)) {
uint_t szc = curproc->p_brkpageszc;
size_t pgsz = page_get_pagesize(szc);
caddr_t ebss = addr + phdr->p_memsz;
size_t extra_zfodsz;
ASSERT(pgsz > PAGESIZE);
extra_zfodsz = P2NPHASE((uintptr_t)ebss, pgsz);
if (error = execmap(vp, addr, phdr->p_filesz,
zfodsz + extra_zfodsz, phdr->p_offset,
prot, page, szc))
goto bad;
if (brksize != NULL)
*brksize = extra_zfodsz;
} else {
if (error = execmap(vp, addr, phdr->p_filesz,
zfodsz, phdr->p_offset, prot, page, 0))
goto bad;
}
if (bssbase != NULL && addr >= *bssbase &&
phdr == dataphdrp) {
*bssbase = addr + phdr->p_filesz;
}
if (brkbase != NULL && addr >= *brkbase) {
*brkbase = addr + phdr->p_memsz;
}
memsz = btopr(phdr->p_memsz);
if ((*execsz + memsz) < *execsz) {
error = ENOMEM;
goto bad;
}
*execsz += memsz;
break;
case PT_INTERP:
if (ptload)
goto bad;
*intphdr = phdr;
break;
case PT_SHLIB:
*stphdr = phdr;
break;
case PT_PHDR:
if (ptload || phdr->p_flags == 0)
goto bad;
if (uphdr != NULL)
*uphdr = phdr;
break;
case PT_NULL:
case PT_DYNAMIC:
case PT_NOTE:
break;
case PT_SUNWDTRACE:
if (dtphdr != NULL)
*dtphdr = phdr;
break;
default:
break;
}
phdr = (Phdr *)((caddr_t)phdr + hsize);
}
if (minaddrp != NULL) {
ASSERT(minaddr != (caddr_t)UINTPTR_MAX);
*minaddrp = (uintptr_t)minaddr;
}
if (brkbase != NULL && secflag_enabled(curproc, PROC_SEC_ASLR)) {
size_t off;
uintptr_t base = (uintptr_t)*brkbase;
uintptr_t oend = base + *brksize;
ASSERT(ISP2(aslr_max_brk_skew));
(void) random_get_pseudo_bytes((uint8_t *)&off, sizeof (off));
base += P2PHASE(off, aslr_max_brk_skew);
base = P2ROUNDUP(base, PAGESIZE);
*brkbase = (caddr_t)base;
if (oend > base)
*brksize = oend - base;
else
*brksize = 0;
}
return (0);
bad:
if (error == 0)
error = EINVAL;
return (error);
}
int
elfnote(vnode_t *vp, offset_t *offsetp, int type, int descsz, void *desc,
rlim64_t rlimit, cred_t *credp)
{
Note note;
int error;
bzero(¬e, sizeof (note));
bcopy("CORE", note.name, 4);
note.nhdr.n_type = type;
note.nhdr.n_namesz = 5;
note.nhdr.n_descsz = roundup(descsz, sizeof (Word));
if (error = core_write(vp, UIO_SYSSPACE, *offsetp, ¬e,
sizeof (note), rlimit, credp))
return (error);
*offsetp += sizeof (note);
if (error = core_write(vp, UIO_SYSSPACE, *offsetp, desc,
note.nhdr.n_descsz, rlimit, credp))
return (error);
*offsetp += note.nhdr.n_descsz;
return (0);
}
static void
elf_copy_scn(elf_core_ctx_t *ctx, const Shdr *src, vnode_t *src_vp, Shdr *dst)
{
size_t n = src->sh_size;
u_offset_t off = 0;
const u_offset_t soff = src->sh_offset;
const u_offset_t doff = ctx->ecc_doffset;
void *buf = ctx->ecc_buf;
vnode_t *dst_vp = ctx->ecc_vp;
cred_t *credp = ctx->ecc_credp;
if (n > OFF_MAX || (n + soff) > OFF_MAX || (n + doff) > OFF_MAX ||
(n + soff) < n || (n + doff) < n) {
dst->sh_size = 0;
dst->sh_offset = 0;
return;
}
while (n != 0) {
const size_t len = MIN(ctx->ecc_bufsz, n);
ssize_t resid;
if (vn_rdwr(UIO_READ, src_vp, buf, (ssize_t)len,
(offset_t)(soff + off),
UIO_SYSSPACE, 0, (rlim64_t)0, credp, &resid) != 0 ||
resid >= len || resid < 0 ||
core_write(dst_vp, UIO_SYSSPACE, (offset_t)(doff + off),
buf, len - resid, ctx->ecc_rlimit, credp) != 0) {
dst->sh_size = 0;
dst->sh_offset = 0;
return;
}
ASSERT(n >= len - resid);
n -= len - resid;
off += len - resid;
}
ctx->ecc_doffset += src->sh_size;
}
static int
elf_process_obj_scns(elf_core_ctx_t *ctx, vnode_t *mvp, caddr_t saddr,
Shdr *v, uint_t idx, const uint_t remain, shstrtab_t *shstrtab,
uint_t *countp)
{
Ehdr ehdr;
const core_content_t content = ctx->ecc_content;
cred_t *credp = ctx->ecc_credp;
Shdr *ctf = NULL, *symtab = NULL, *strtab = NULL;
uintptr_t off = 0;
uint_t nshdrs, shstrndx, nphdrs, count, extra;
u_offset_t *doffp = &ctx->ecc_doffset;
boolean_t ctf_link = B_FALSE;
caddr_t shbase;
size_t shsize, shstrsize;
char *shstrbase;
int error = 0;
const boolean_t justcounting = (v == NULL);
ASSERT3U(remain, <, UINT_MAX);
*countp = count = 0;
if ((content &
(CC_CONTENT_CTF | CC_CONTENT_SYMTAB | CC_CONTENT_DEBUG)) == 0) {
return (0);
}
if (getelfhead(mvp, credp, &ehdr, &nshdrs, &shstrndx, &nphdrs) != 0 ||
getelfshdr(mvp, credp, &ehdr, nshdrs, shstrndx, &shbase, &shsize,
&shstrbase, &shstrsize) != 0) {
return (0);
}
off = ehdr.e_shentsize;
for (uint_t i = 1; i < nshdrs; i++, off += ehdr.e_shentsize) {
Shdr *shdr, *symchk = NULL, *strchk;
const char *name;
shdr = (Shdr *)(shbase + off);
if (shdr->sh_name >= shstrsize || shdr->sh_type == SHT_NULL)
continue;
name = shstrbase + shdr->sh_name;
if (ctf == NULL && (content & CC_CONTENT_CTF) != 0 &&
strcmp(name, shstrtab_data[STR_CTF]) == 0) {
ctf = shdr;
if (ctf->sh_link != 0 && ctf->sh_link < nshdrs) {
symchk = (Shdr *)(shbase +
shdr->sh_link * ehdr.e_shentsize);
ctf_link = B_TRUE;
} else {
continue;
}
} else if (symtab == NULL &&
(content & CC_CONTENT_SYMTAB) != 0 &&
strcmp(name, shstrtab_data[STR_SYMTAB]) == 0) {
symchk = shdr;
} else if ((content & CC_CONTENT_DEBUG) != 0 &&
strncmp(name, ".debug_", strlen(".debug_")) == 0) {
count++;
if (count > remain) {
error = ENOMEM;
goto done;
}
if (justcounting)
continue;
elf_ctx_resize_scratch(ctx, shdr->sh_size);
if (!shstrtab_ndx(shstrtab, name, &v[idx].sh_name)) {
error = ENOMEM;
goto done;
}
v[idx].sh_addr = (Addr)(uintptr_t)saddr;
v[idx].sh_type = shdr->sh_type;
v[idx].sh_addralign = shdr->sh_addralign;
*doffp = roundup(*doffp, v[idx].sh_addralign);
v[idx].sh_offset = *doffp;
v[idx].sh_size = shdr->sh_size;
v[idx].sh_link = 0;
v[idx].sh_entsize = shdr->sh_entsize;
v[idx].sh_info = shdr->sh_info;
elf_copy_scn(ctx, shdr, mvp, &v[idx]);
idx++;
continue;
} else {
continue;
}
ASSERT(symchk != NULL);
if ((symchk->sh_type != SHT_DYNSYM &&
symchk->sh_type != SHT_SYMTAB) ||
symchk->sh_link == 0 || symchk->sh_link >= nshdrs) {
ctf_link = B_FALSE;
continue;
}
strchk = (Shdr *)(shbase + symchk->sh_link * ehdr.e_shentsize);
if (strchk->sh_type != SHT_STRTAB) {
ctf_link = B_FALSE;
continue;
}
symtab = symchk;
strtab = strchk;
if (symtab != NULL && ctf != NULL &&
(content & CC_CONTENT_DEBUG) == 0) {
break;
}
}
extra = 0;
if (ctf != NULL)
extra += 1;
if (symtab != NULL)
extra += 2;
if (remain < extra || count > remain - extra) {
error = ENOMEM;
goto done;
}
count += extra;
if (justcounting)
goto done;
if (ctf != NULL) {
elf_ctx_resize_scratch(ctx, ctf->sh_size);
if (!shstrtab_ndx(shstrtab,
shstrtab_data[STR_CTF], &v[idx].sh_name)) {
error = ENOMEM;
goto done;
}
v[idx].sh_addr = (Addr)(uintptr_t)saddr;
v[idx].sh_type = SHT_PROGBITS;
v[idx].sh_addralign = 4;
*doffp = roundup(*doffp, v[idx].sh_addralign);
v[idx].sh_offset = *doffp;
v[idx].sh_size = ctf->sh_size;
if (ctf_link) {
v[idx].sh_link = idx + 1;
ASSERT(symtab != NULL);
} else {
v[idx].sh_link = 0;
}
elf_copy_scn(ctx, ctf, mvp, &v[idx]);
idx++;
}
if (symtab != NULL) {
shstrtype_t symtab_type, strtab_type;
uint_t symtab_name, strtab_name;
elf_ctx_resize_scratch(ctx,
MAX(symtab->sh_size, strtab->sh_size));
if (symtab->sh_type == SHT_DYNSYM) {
symtab_type = STR_DYNSYM;
strtab_type = STR_DYNSTR;
} else {
symtab_type = STR_SYMTAB;
strtab_type = STR_STRTAB;
}
if (!shstrtab_ndx(shstrtab,
shstrtab_data[symtab_type], &symtab_name)) {
error = ENOMEM;
goto done;
}
if (!shstrtab_ndx(shstrtab,
shstrtab_data[strtab_type], &strtab_name)) {
error = ENOMEM;
goto done;
}
v[idx].sh_name = symtab_name;
v[idx].sh_type = symtab->sh_type;
v[idx].sh_addr = symtab->sh_addr;
if (ehdr.e_type == ET_DYN || v[idx].sh_addr == 0)
v[idx].sh_addr += (Addr)(uintptr_t)saddr;
v[idx].sh_addralign = symtab->sh_addralign;
*doffp = roundup(*doffp, v[idx].sh_addralign);
v[idx].sh_offset = *doffp;
v[idx].sh_size = symtab->sh_size;
v[idx].sh_link = idx + 1;
v[idx].sh_entsize = symtab->sh_entsize;
v[idx].sh_info = symtab->sh_info;
elf_copy_scn(ctx, symtab, mvp, &v[idx]);
idx++;
v[idx].sh_name = strtab_name;
v[idx].sh_type = SHT_STRTAB;
v[idx].sh_flags = SHF_STRINGS;
v[idx].sh_addr = strtab->sh_addr;
if (ehdr.e_type == ET_DYN || v[idx].sh_addr == 0)
v[idx].sh_addr += (Addr)(uintptr_t)saddr;
v[idx].sh_addralign = strtab->sh_addralign;
*doffp = roundup(*doffp, v[idx].sh_addralign);
v[idx].sh_offset = *doffp;
v[idx].sh_size = strtab->sh_size;
elf_copy_scn(ctx, strtab, mvp, &v[idx]);
idx++;
}
done:
kmem_free(shstrbase, shstrsize);
kmem_free(shbase, shsize);
if (error == 0)
*countp = count;
return (error);
}
static int
elf_process_scns(elf_core_ctx_t *ctx, Shdr *v, const uint_t nv, uint_t *nshdrsp)
{
vnode_t *lastvp = NULL;
struct seg *seg;
uint_t remain, idx;
shstrtab_t shstrtab;
struct as *as = ctx->ecc_p->p_as;
int error = 0;
const boolean_t justcounting = (v == NULL);
ASSERT(AS_WRITE_HELD(as));
if (justcounting) {
ASSERT(nv == 0);
remain = UINT_MAX;
} else {
ASSERT(nv != 0);
remain = nv;
if (!shstrtab_init(&shstrtab))
return (ENOMEM);
}
idx = 1;
remain--;
for (seg = AS_SEGFIRST(as); seg != NULL; seg = AS_SEGNEXT(as, seg)) {
vnode_t *mvp;
void *tmp = NULL;
caddr_t saddr = seg->s_base, naddr, eaddr;
size_t segsize;
uint_t count, prot;
if (seg->s_ops != &segvn_ops ||
SEGOP_GETVP(seg, seg->s_base, &mvp) != 0 ||
mvp == lastvp || mvp == NULL || mvp->v_type != VREG ||
(segsize = pr_getsegsize(seg, 1)) == 0) {
continue;
}
eaddr = saddr + segsize;
prot = pr_getprot(seg, 1, &tmp, &saddr, &naddr, eaddr);
pr_getprot_done(&tmp);
if ((prot & (PROT_WRITE | PROT_EXEC)) != PROT_EXEC)
continue;
error = elf_process_obj_scns(ctx, mvp, saddr, v, idx, remain,
&shstrtab, &count);
if (error != 0)
goto done;
VERIFY3U(count, <=, remain);
if (!justcounting) {
VERIFY3U(idx + count, <=, nv);
}
remain -= count;
idx += count;
lastvp = mvp;
}
if (justcounting) {
if (idx == 1) {
*nshdrsp = 0;
} else if (remain < 1) {
*nshdrsp = 0;
return (ENOMEM);
} else {
*nshdrsp = idx + 1;
}
return (0);
}
if (remain != 1) {
cmn_err(CE_WARN, "elfcore: core dump failed for "
"process %d; address space is changing",
ctx->ecc_p->p_pid);
error = EIO;
goto done;
}
if (!shstrtab_ndx(&shstrtab, shstrtab_data[STR_SHSTRTAB],
&v[idx].sh_name)) {
error = ENOMEM;
goto done;
}
v[idx].sh_size = shstrtab_size(&shstrtab);
v[idx].sh_addralign = 1;
v[idx].sh_offset = ctx->ecc_doffset;
v[idx].sh_flags = SHF_STRINGS;
v[idx].sh_type = SHT_STRTAB;
elf_ctx_resize_scratch(ctx, v[idx].sh_size);
VERIFY3U(ctx->ecc_bufsz, >=, v[idx].sh_size);
shstrtab_dump(&shstrtab, ctx->ecc_buf);
error = core_write(ctx->ecc_vp, UIO_SYSSPACE, ctx->ecc_doffset,
ctx->ecc_buf, v[idx].sh_size, ctx->ecc_rlimit, ctx->ecc_credp);
if (error == 0)
ctx->ecc_doffset += v[idx].sh_size;
done:
if (!justcounting)
shstrtab_fini(&shstrtab);
return (error);
}
int
elfcore(vnode_t *vp, proc_t *p, cred_t *credp, rlim64_t rlimit, int sig,
core_content_t content)
{
u_offset_t poffset, soffset, doffset;
int error;
uint_t i, nphdrs, nshdrs;
struct seg *seg;
struct as *as = p->p_as;
void *bigwad, *zeropg = NULL;
size_t bigsize, phdrsz, shdrsz;
Ehdr *ehdr;
Phdr *phdr;
Shdr shdr0;
caddr_t brkbase, stkbase;
size_t brksize, stksize;
boolean_t overflowed = B_FALSE, retried = B_FALSE;
klwp_t *lwp = ttolwp(curthread);
elf_core_ctx_t ctx = {
.ecc_vp = vp,
.ecc_p = p,
.ecc_credp = credp,
.ecc_rlimit = rlimit,
.ecc_content = content,
.ecc_doffset = 0,
.ecc_buf = NULL,
.ecc_bufsz = 0
};
top:
ASSERT(p == ttoproc(curthread));
prstop(0, 0);
AS_LOCK_ENTER(as, RW_WRITER);
nphdrs = prnsegs(as, 0) + 2;
nshdrs = error = 0;
if (content & (CC_CONTENT_CTF | CC_CONTENT_SYMTAB | CC_CONTENT_DEBUG))
error = elf_process_scns(&ctx, NULL, 0, &nshdrs);
AS_LOCK_EXIT(as);
if (error != 0)
return (error);
if (nshdrs == 0 && nphdrs >= PN_XNUM)
nshdrs = 1;
VERIFY3U(nphdrs, >=, 2);
phdrsz = nphdrs * sizeof (Phdr);
shdrsz = nshdrs * sizeof (Shdr);
bigsize = MAX(sizeof (Ehdr), MAX(phdrsz, shdrsz));
bigwad = kmem_alloc(bigsize, KM_SLEEP);
ehdr = (Ehdr *)bigwad;
bzero(ehdr, sizeof (*ehdr));
ehdr->e_ident[EI_MAG0] = ELFMAG0;
ehdr->e_ident[EI_MAG1] = ELFMAG1;
ehdr->e_ident[EI_MAG2] = ELFMAG2;
ehdr->e_ident[EI_MAG3] = ELFMAG3;
ehdr->e_ident[EI_CLASS] = ELFCLASS;
ehdr->e_type = ET_CORE;
#if !defined(_LP64) || defined(_ELF32_COMPAT)
#if defined(__sparc)
ehdr->e_ident[EI_DATA] = ELFDATA2MSB;
ehdr->e_machine = EM_SPARC;
#elif defined(__i386_COMPAT)
ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
ehdr->e_machine = EM_386;
#else
#error "no recognized machine type is defined"
#endif
#else
#if defined(__sparc)
ehdr->e_ident[EI_DATA] = ELFDATA2MSB;
ehdr->e_machine = EM_SPARCV9;
#elif defined(__amd64)
ehdr->e_ident[EI_DATA] = ELFDATA2LSB;
ehdr->e_machine = EM_AMD64;
#else
#error "no recognized 64-bit machine type is defined"
#endif
#endif
poffset = sizeof (Ehdr);
soffset = sizeof (Ehdr) + phdrsz;
doffset = sizeof (Ehdr) + phdrsz + shdrsz;
bzero(&shdr0, sizeof (shdr0));
if (nphdrs >= PN_XNUM) {
ehdr->e_phnum = PN_XNUM;
shdr0.sh_info = nphdrs;
} else {
ehdr->e_phnum = (unsigned short)nphdrs;
}
if (nshdrs > 0) {
if (nshdrs >= SHN_LORESERVE) {
ehdr->e_shnum = 0;
shdr0.sh_size = nshdrs;
} else {
ehdr->e_shnum = (unsigned short)nshdrs;
}
if (nshdrs - 1 >= SHN_LORESERVE) {
ehdr->e_shstrndx = SHN_XINDEX;
shdr0.sh_link = nshdrs - 1;
} else {
ehdr->e_shstrndx = (unsigned short)(nshdrs - 1);
}
ehdr->e_shoff = soffset;
ehdr->e_shentsize = sizeof (Shdr);
}
ehdr->e_ident[EI_VERSION] = EV_CURRENT;
ehdr->e_version = EV_CURRENT;
ehdr->e_ehsize = sizeof (Ehdr);
ehdr->e_phoff = poffset;
ehdr->e_phentsize = sizeof (Phdr);
if (error = core_write(vp, UIO_SYSSPACE, (offset_t)0, ehdr,
sizeof (Ehdr), rlimit, credp)) {
goto done;
}
phdr = (Phdr *)bigwad;
bzero(phdr, phdrsz);
setup_old_note_header(&phdr[0], p);
phdr[0].p_offset = doffset = roundup(doffset, sizeof (Word));
doffset += phdr[0].p_filesz;
setup_note_header(&phdr[1], p);
phdr[1].p_offset = doffset = roundup(doffset, sizeof (Word));
doffset += phdr[1].p_filesz;
mutex_enter(&p->p_lock);
brkbase = p->p_brkbase;
brksize = p->p_brksize;
stkbase = p->p_usrstack - p->p_stksize;
stksize = p->p_stksize;
mutex_exit(&p->p_lock);
AS_LOCK_ENTER(as, RW_WRITER);
i = 2;
for (seg = AS_SEGFIRST(as); seg != NULL; seg = AS_SEGNEXT(as, seg)) {
caddr_t eaddr = seg->s_base + pr_getsegsize(seg, 0);
caddr_t saddr, naddr;
void *tmp = NULL;
extern struct seg_ops segspt_shmops;
if ((seg->s_flags & S_HOLE) != 0) {
continue;
}
for (saddr = seg->s_base; saddr < eaddr; saddr = naddr) {
uint_t prot;
size_t size;
int type;
vnode_t *mvp;
prot = pr_getprot(seg, 0, &tmp, &saddr, &naddr, eaddr);
prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
if ((size = (size_t)(naddr - saddr)) == 0) {
ASSERT(tmp == NULL);
continue;
} else if (i == nphdrs) {
pr_getprot_done(&tmp);
overflowed = B_TRUE;
break;
}
phdr[i].p_type = PT_LOAD;
phdr[i].p_vaddr = (Addr)(uintptr_t)saddr;
phdr[i].p_memsz = size;
if (prot & PROT_READ)
phdr[i].p_flags |= PF_R;
if (prot & PROT_WRITE)
phdr[i].p_flags |= PF_W;
if (prot & PROT_EXEC)
phdr[i].p_flags |= PF_X;
type = SEGOP_GETTYPE(seg, saddr);
if (saddr == stkbase && size == stksize) {
if (!(content & CC_CONTENT_STACK))
goto exclude;
} else if (saddr == brkbase && size == brksize) {
if (!(content & CC_CONTENT_HEAP))
goto exclude;
} else if (seg->s_ops == &segspt_shmops) {
if (type & MAP_NORESERVE) {
if (!(content & CC_CONTENT_DISM))
goto exclude;
} else {
if (!(content & CC_CONTENT_ISM))
goto exclude;
}
} else if (seg->s_ops != &segvn_ops) {
goto exclude;
} else if (type & MAP_SHARED) {
if (shmgetid(p, saddr) != SHMID_NONE) {
if (!(content & CC_CONTENT_SHM))
goto exclude;
} else if (SEGOP_GETVP(seg, seg->s_base,
&mvp) != 0 || mvp == NULL ||
mvp->v_type != VREG) {
if (!(content & CC_CONTENT_SHANON))
goto exclude;
} else {
if (!(content & CC_CONTENT_SHFILE))
goto exclude;
}
} else if (SEGOP_GETVP(seg, seg->s_base, &mvp) != 0 ||
mvp == NULL || mvp->v_type != VREG) {
if (!(content & CC_CONTENT_ANON))
goto exclude;
} else if (prot == (PROT_READ | PROT_EXEC)) {
if (!(content & CC_CONTENT_TEXT))
goto exclude;
} else if (prot == PROT_READ) {
if (!(content & CC_CONTENT_RODATA))
goto exclude;
} else {
if (!(content & CC_CONTENT_DATA))
goto exclude;
}
doffset = roundup(doffset, sizeof (Word));
phdr[i].p_offset = doffset;
phdr[i].p_filesz = size;
doffset += size;
exclude:
i++;
}
VERIFY(tmp == NULL);
if (overflowed)
break;
}
AS_LOCK_EXIT(as);
if (overflowed || i != nphdrs) {
if (!retried) {
retried = B_TRUE;
overflowed = B_FALSE;
kmem_free(bigwad, bigsize);
goto top;
}
cmn_err(CE_WARN, "elfcore: core dump failed for "
"process %d; address space is changing", p->p_pid);
error = EIO;
goto done;
}
if ((error = core_write(vp, UIO_SYSSPACE, poffset,
phdr, phdrsz, rlimit, credp)) != 0) {
goto done;
}
if ((error = write_old_elfnotes(p, sig, vp, phdr[0].p_offset, rlimit,
credp)) != 0) {
goto done;
}
if ((error = write_elfnotes(p, sig, vp, phdr[1].p_offset, rlimit,
credp, content)) != 0) {
goto done;
}
for (i = 2; i < nphdrs; i++) {
prkillinfo_t killinfo;
sigqueue_t *sq;
int sig, j;
if (phdr[i].p_filesz == 0)
continue;
if ((phdr[i].p_flags & (PF_R | PF_W | PF_X)) == 0) {
size_t towrite = phdr[i].p_filesz;
size_t curoff = 0;
if (zeropg == NULL) {
zeropg = kmem_zalloc(elf_zeropg_sz, KM_SLEEP);
}
error = 0;
while (towrite != 0) {
size_t len = MIN(towrite, elf_zeropg_sz);
error = core_write(vp, UIO_SYSSPACE,
phdr[i].p_offset + curoff, zeropg, len,
rlimit, credp);
if (error != 0)
break;
towrite -= len;
curoff += len;
}
} else {
error = core_seg(p, vp, phdr[i].p_offset,
(caddr_t)(uintptr_t)phdr[i].p_vaddr,
phdr[i].p_filesz, rlimit, credp);
}
if (error == 0)
continue;
if ((sig = lwp->lwp_cursig) == 0) {
(void) core_write(vp, UIO_SYSSPACE, phdr[i].p_offset,
&error, sizeof (error), rlimit, credp);
phdr[i].p_filesz = 0;
phdr[i].p_flags |= PF_SUNW_FAILURE;
if ((error = core_write(vp, UIO_SYSSPACE,
poffset + sizeof (Phdr) * i, &phdr[i],
sizeof (Phdr), rlimit, credp)) != 0)
goto done;
continue;
}
bzero(&killinfo, sizeof (killinfo));
killinfo.prk_error = EINTR;
sq = sig == SIGKILL ? curproc->p_killsqp : lwp->lwp_curinfo;
if (sq != NULL) {
bcopy(&sq->sq_info, &killinfo.prk_info,
sizeof (sq->sq_info));
} else {
killinfo.prk_info.si_signo = lwp->lwp_cursig;
killinfo.prk_info.si_code = SI_NOINFO;
}
#if (defined(_SYSCALL32_IMPL) || defined(_LP64))
if (curproc->p_model == DATAMODEL_ILP32) {
siginfo32_t si32;
siginfo_kto32((k_siginfo_t *)&killinfo.prk_info, &si32);
bcopy(&si32, &killinfo.prk_info, sizeof (si32));
}
#endif
(void) core_write(vp, UIO_SYSSPACE, phdr[i].p_offset,
&killinfo, sizeof (killinfo), rlimit, credp);
phdr[i].p_filesz = 0;
phdr[i].p_flags |= PF_SUNW_FAILURE | PF_SUNW_KILLED |
PF_SUNW_SIGINFO;
for (j = i + 1; j < nphdrs; j++) {
phdr[j].p_filesz = 0;
phdr[j].p_flags |= PF_SUNW_FAILURE | PF_SUNW_KILLED;
}
if ((error = core_write(vp, UIO_SYSSPACE,
poffset + sizeof (Phdr) * i, &phdr[i],
sizeof (Phdr) * (nphdrs - i), rlimit, credp)) != 0) {
goto done;
}
break;
}
if (nshdrs > 0) {
Shdr *shdr = (Shdr *)bigwad;
bzero(shdr, shdrsz);
if (nshdrs > 1) {
ctx.ecc_doffset = doffset;
AS_LOCK_ENTER(as, RW_WRITER);
error = elf_process_scns(&ctx, shdr, nshdrs, NULL);
AS_LOCK_EXIT(as);
if (error != 0)
goto done;
}
bcopy(&shdr0, shdr, sizeof (shdr0));
error = core_write(vp, UIO_SYSSPACE, soffset, shdr, shdrsz,
rlimit, credp);
}
done:
if (zeropg != NULL)
kmem_free(zeropg, elf_zeropg_sz);
if (ctx.ecc_bufsz != 0)
kmem_free(ctx.ecc_buf, ctx.ecc_bufsz);
kmem_free(bigwad, bigsize);
return (error);
}
#ifndef _ELF32_COMPAT
static struct execsw esw = {
#ifdef _LP64
elf64magicstr,
#else
elf32magicstr,
#endif
0,
5,
elfexec,
elfcore
};
static struct modlexec modlexec = {
&mod_execops, "exec module for elf", &esw
};
#ifdef _LP64
extern int elf32exec(vnode_t *vp, execa_t *uap, uarg_t *args,
intpdata_t *idatap, int level, size_t *execsz,
int setid, caddr_t exec_file, cred_t *cred,
int brand_action);
extern int elf32core(vnode_t *vp, proc_t *p, cred_t *credp,
rlim64_t rlimit, int sig, core_content_t content);
static struct execsw esw32 = {
elf32magicstr,
0,
5,
elf32exec,
elf32core
};
static struct modlexec modlexec32 = {
&mod_execops, "32-bit exec module for elf", &esw32
};
#endif
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlexec,
#ifdef _LP64
(void *)&modlexec32,
#endif
NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
#endif