/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* LINTLIBRARY */ #include #include #include #include #include #include #include #include #include #include #include #include "rtld.h" #include "rtc.h" #include "_crle.h" #include "msg.h" /* * Routines for dumping alternate objects under CRLE_AUD_DLDUMP mode. */ static Addr membgn = 0; static Addr memend = 0; /* * For each file in the configuration file that requires an alternate (dldump()) * version, add the object to the processes main link-map. The process head * may be an application, shared object, or lddstub. In any case this object * may be augmented with other objects defined within the configuration file. * * Each file is initially loaded with RTLD_CONFGEN so that no dependency * analysis, relocation, or user code (.init's) is executed. By skipping * analysis we save time and allow for a family of objects to be dumped that * may not have all relocations satisfied. If necessary, a later call to * dlopen() using RTLD_NOW will force relocations to occur. * * A mapping range is maintained to span the mapping of each objects, and this * range is finally written back to the caller. */ static int /* ARGSUSED1 */ load(const char *opath, const char *npath) { Grp_hdl * ghp; Rt_map * lmp; Addr _membgn, _memend; if ((ghp = (Grp_hdl *)dlmopen(LM_ID_BASE, opath, (RTLD_LAZY | RTLD_GLOBAL | RTLD_CONFGEN))) == NULL) { (void) fprintf(stderr, MSG_INTL(MSG_DL_OPEN), MSG_ORIG(MSG_FIL_LIBCRLE), dlerror()); return (1); } lmp = ghp->gh_ownlmp; FLAGS1(lmp) |= FL1_RT_CONFSET; /* * Establish the mapping range of the objects dumped so far. */ _membgn = ADDR(lmp); _memend = (ADDR(lmp) + MSIZE(lmp)); if (membgn == 0) { membgn = _membgn; memend = _memend; } else { if (membgn > _membgn) membgn = _membgn; if (memend < _memend) memend = _memend; } return (0); } /* * dldump(3x) an object that is already part of the main link-map list. */ static int dump(const char *opath, const char *npath) { (void) unlink(npath); if (dldump(opath, npath, dlflag) != 0) { (void) fprintf(stderr, MSG_INTL(MSG_DL_DUMP), MSG_ORIG(MSG_FIL_LIBCRLE), dlerror()); return (1); } return (0); } /* * Traverse a configuration file directory/file list. Each file within the * list is maintained as both a full pathname and a simple filename - we're * only interested in one. * * This rutine is called twice, once to insure the appropriate objects are * mapped in (fptr == load()) and then once again to dldump(3x) the mapped * objects (fptr == dump()). */ static int scanconfig(Addr addr, int (*fptr)()) { Rtc_head * head = (Rtc_head *)addr; Rtc_obj * obj; Rtc_dir * dirtbl; Rtc_file * filetbl; const char *str, *strtbl; /* LINTED */ strtbl = (const char *)((char *)addr + head->ch_str); /* * Scan the directory and filename arrays looking for alternatives. */ for (dirtbl = (Rtc_dir *)(head->ch_dir + addr); dirtbl->cd_obj; dirtbl++) { obj = (Rtc_obj *)(dirtbl->cd_obj + addr); str = strtbl + obj->co_name; if (obj->co_flags & RTC_OBJ_NOEXIST) continue; for (filetbl = (Rtc_file *)(dirtbl->cd_file + addr); filetbl->cf_obj; filetbl++) { obj = (Rtc_obj *)(filetbl->cf_obj + addr); str = strtbl + obj->co_name; if ((obj->co_flags & (RTC_OBJ_DUMP | RTC_OBJ_REALPTH | RTC_OBJ_EXEC)) == (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) { if ((*fptr)(str, strtbl + obj->co_alter) != 0) return (1); } } } /* * Are we dumping a specific application. */ if (head->ch_app) { if (fptr == load) { Grp_hdl * ghp; /* * Obtain a handle to the application and set the * FL1_RT_CONFSET flag. */ if ((ghp = dlmopen(LM_ID_BASE, 0, (RTLD_NOLOAD | RTLD_CONFGEN))) == 0) return (1); FLAGS1(ghp->gh_ownlmp) |= FL1_RT_CONFSET; } else { /* * If we're dumping and this configuration is for a * specific application dump it also. */ /* LINTED */ obj = (Rtc_obj *)((char *)addr + head->ch_app); str = strtbl + obj->co_alter; if (dump((const char *)0, str) != 0) return (1); } } return (0); } /* * Before loading any dependencies determine the present memory mappings being * used and fill any holes between these mappings. This insures that all * dldump()'ed dependencies will live in a single consecutive address range. */ int filladdr(void) { prmap_t *maps, *_maps; struct stat status; int fd = 0, err, num, _num; size_t size, syspagsz; uintptr_t laddr = 0, saddr; pstatus_t prstatus; /* * Open /proc/self/status to determine the virtual address of the * process heap. */ if ((fd = open(MSG_ORIG(MSG_PTH_PROCSTATUS), O_RDONLY)) == -1) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_PROCSTATUS), strerror(err)); return (1); } if (read(fd, &prstatus, sizeof (pstatus_t)) != sizeof (pstatus_t)) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_READ), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_PROCSTATUS), strerror(err)); (void) close(fd); return (1); } (void) close(fd); /* * Round the process heap to the next page boundary so that it can be * used to isolated the a.out mappings (pr_brkbase typically occurs * at the end, but within, the a.out's data segment). As libcrle is * used as an audit library, no process user code has run so there * can't be any heap. pr_brksize is added here for completeness. */ syspagsz = sysconf(_SC_PAGESIZE); saddr = M_PROUND(prstatus.pr_brkbase + prstatus.pr_brksize); /* * Open /proc/self/rmap to obtain the processes reserved mappings. */ if ((fd = open(MSG_ORIG(MSG_PTH_PROCRMAP), O_RDONLY)) == -1) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_PROCRMAP), strerror(err)); return (1); } (void) fstat(fd, &status); /* * Determine number of mappings - use alloca so as not to perturb any * mapping information by a malloc, which itself might add a mapping. */ /* LINTED */ num = (int)(status.st_size / sizeof (prmap_t)); size = num * sizeof (prmap_t); if ((maps = alloca(size)) == 0) { (void) fprintf(stderr, MSG_INTL(MSG_SYS_ALLOC), MSG_ORIG(MSG_FIL_LIBCRLE), strerror(ENOMEM)); (void) close(pfd); return (1); } if (read(fd, (void *)maps, size) < 0) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_READ), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_PROCRMAP), strerror(err)); (void) close(fd); return (1); } (void) close(fd); /* * Use /dev/null for filling holes. */ if ((fd = open(MSG_ORIG(MSG_PTH_DEVNULL), O_RDONLY)) == -1) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_OPEN), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_DEVNULL), strerror(err)); return (1); } /* * Scan each mapping - note it is assummed that the mappings are * presented in order. We fill holes between mappings. On intel * the last mapping is usually the data segment of ld.so.1, after * this comes a red zone into which non-fixed mapping won't get * place. Thus we can simply bail from the loop after seeing the * last mapping. */ for (_num = 0, _maps = maps; _num < num; _num++, _maps++) { /* * Skip all mappings below brkbase, these represent the a.out * (and the stack on intel). */ if ((laddr == 0) && ((_maps->pr_vaddr + _maps->pr_size) <= saddr)) continue; /* * For each consecutive mapping determine the hole between each * and fill it from /dev/null. */ if (laddr == 0) { laddr = _maps->pr_vaddr + _maps->pr_size; continue; } if ((size = _maps->pr_vaddr - laddr) != 0) { if (mmap((void *)laddr, size, PROT_NONE, (MAP_FIXED | MAP_PRIVATE), fd, 0) == MAP_FAILED) { err = errno; (void) fprintf(stderr, MSG_INTL(MSG_SYS_MMAP), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_DEVNULL), strerror(err)); return (1); } } laddr = _maps->pr_vaddr + _maps->pr_size; } /* * It's been observed that there may be space between the end of the * last mapping (typically ld.so.1), and the kernel base address. As * there's no interface to determine the kernel base address, keep * filling in pages until we get an error. We'll get ENOMEM once we * hit the kernel base address. */ while (laddr) { if (mmap((void *)laddr, syspagsz, PROT_NONE, (MAP_FIXED | MAP_PRIVATE), fd, 0) == MAP_FAILED) { err = errno; if (err == ENOMEM) break; (void) fprintf(stderr, MSG_INTL(MSG_SYS_MMAP), MSG_ORIG(MSG_FIL_LIBCRLE), MSG_ORIG(MSG_PTH_DEVNULL), strerror(err)); return (1); } laddr += syspagsz; } /* * Close /dev/null. */ (void) close(fd); return (0); } /* * Dump alternative objects as part of building a configuration file. A temp * configuration is already built and made available to the process, and is * located via dlinfo(). Having load()'ed each object, and dump()'ed its image, * the final memory reservation infoamtion is returned to the caller. */ int dumpconfig(void) { char buffer[PATH_MAX]; Addr config; Dl_info info; /* * Determine the configuration file and where it is mapped. */ if (dlinfo((void *)NULL, RTLD_DI_CONFIGADDR, &info) == -1) { (void) fprintf(stderr, MSG_INTL(MSG_DL_INFO), MSG_ORIG(MSG_FIL_LIBCRLE), dlerror()); return (1); } config = (Addr)info.dli_fbase; /* * Scan the configuration file for alternative entries. */ if (scanconfig(config, load) != 0) return (1); /* * Having mapped all objects, relocate them. It would be nice if we * could drop this step altogether, and have dldump() carry out just * those relocations required, but when binding to an application we * need to handle copy relocations - these can affect bindings (in the * case of things like libld.so which have direct bindings) and require * that the data being copied is itself relocated. */ if (dlmopen(LM_ID_BASE, 0, (RTLD_NOW | RTLD_CONFGEN)) == 0) return (1); /* * Rescan the configuration dumping out each alternative file. */ if (scanconfig(config, dump) != 0) return (1); /* * Having established the memory range of the dumped images and * sucessfully dumped them out, report back to the caller. */ (void) sprintf(buffer, MSG_ORIG(MSG_AUD_RESBGN), EC_ADDR(membgn)); (void) write(pfd, buffer, strlen(buffer)); (void) sprintf(buffer, MSG_ORIG(MSG_AUD_RESEND), EC_ADDR(memend)); (void) write(pfd, buffer, strlen(buffer)); return (0); }