/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" static Config _config = { 0 }; Config *config = &_config; /* * Validate a configuration file. */ static void elf_config_validate(Addr addr, Rtc_head *head, Rt_map *lmp) { Lm_list *lml = LIST(lmp); const char *str, *strtbl = config->c_strtbl; Rtc_obj *obj; Rtc_dir *dirtbl; Rtc_file *filetbl; rtld_stat_t status; int err; /* * If this configuration file is for a specific application make sure * we've been invoked by the application. Note that we only check the * basename component of the application as the original application * and its cached equivalent are never going to have the same pathnames. * Also, we use PATHNAME() and not NAME() - this catches things like vi * that exec shells using execv(/usr/bin/ksh, sh ...). */ if (head->ch_app) { char *_str, *_cname, *cname; const char *aname = PATHNAME(lmp); obj = (Rtc_obj *)(head->ch_app + addr); cname = _cname = (char *)(strtbl + obj->co_name); if ((_str = strrchr(aname, '/')) != NULL) aname = ++_str; if ((_str = strrchr(cname, '/')) != NULL) cname = ++_str; if (strcmp(aname, cname)) { /* * It's possible a user is trying to ldd(1) an alternate * shared object and point to a configuration file that * the shared object is part of. In this case ignore * any mismatch name warnings. */ if ((lml->lm_flags & LML_FLG_TRC_ENABLE) && ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0)) { eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_APP), config->c_name, _cname); return; } } /* * If we have a valid alternative application reset its original * name for possible $ORIGIN processing. */ if ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0) { ORIGNAME(lmp) = _cname; DIRSZ(lmp) = cname - _cname - 1; } } /* * If alternative objects are specified traverse the directories * specified in the configuration file, if any directory is newer than * the time it was recorded in the cache then continue to inspect its * files. Any file determined newer than its configuration recording * questions the the use of any alternative objects. The intent here * is to make sure no-one abuses a configuration as a means of static * linking. */ for (dirtbl = (Rtc_dir *)(head->ch_dir + addr); dirtbl->cd_obj; dirtbl++) { /* * Skip directories that provide no files - this also catches * RTC_OBJ_NOEXIST directories. */ filetbl = (Rtc_file *)(dirtbl->cd_file + addr); if (filetbl->cf_obj == NULL) continue; /* * Skip directories that haven't provided real, dumped files. */ obj = (Rtc_obj *)(dirtbl->cd_obj + addr); if ((obj->co_flags & (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) != (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) continue; str = strtbl + obj->co_name; if (rtld_stat(str, &status) != 0) { err = errno; eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_DSTAT), config->c_name, str, strerror(err)); continue; } if (status.st_mtime == obj->co_info) continue; /* * The system directory is newer than the configuration files * entry, start checking any dumped files. */ for (; filetbl->cf_obj; filetbl++) { obj = (Rtc_obj *)(filetbl->cf_obj + addr); str = strtbl + obj->co_name; /* * Skip any files that aren't real, dumped files. */ if ((obj->co_flags & (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) != (RTC_OBJ_DUMP | RTC_OBJ_REALPTH)) continue; if (rtld_stat(str, &status) != 0) { err = errno; eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_FSTAT), config->c_name, str, strerror(err)); continue; } /* * If the files size is different somethings been * changed. */ if (status.st_size != obj->co_info) { eprintf(lml, ERR_WARNING, MSG_INTL(MSG_CONF_FCMP), config->c_name, str); } } } } /* * Process a configuration file. * * A configuration file can be specified using the LD_CONFIG environment * variable, from a DT_CONFIG string recorded in the executable (see ld(1) -c), * or in the case of a crle() dumped image, the file is "fabricated" to a * configuration file that may have been associated with the dumped image. In * the absence of any of these techniques, a default configuration file is used. * * The LD_CONFIG variable take precedence, unless the application is secure, in * which case the environment variable is ignored (see ld_generic_env()). * * A DT_CONFIG string is honored, even if the application is secure. However, * the path name follows the same rules as RUNPATH's, which must be a full path * name with no use of $ORIGIN. */ int elf_config(Rt_map *lmp, int aout) { Rtc_id *id; Rtc_head *head; int fd, features = 0; rtld_stat_t status; Addr addr; const char *str; char path[PATH_MAX]; /* * If we're dealing with an alternative application, fabricate the need * for a $ORIGIN/ld.config.app-name configuration file. */ if (rtld_flags & RT_FL_CONFAPP) { if ((str = strrchr(PATHNAME(lmp), '/')) != NULL) str++; else str = PATHNAME(lmp); (void) snprintf(path, PATH_MAX, MSG_ORIG(MSG_ORG_CONFIG), str); str = path; } else str = config->c_name; /* * If a configuration file name is known, expand and verify the name. */ if (str) { size_t size = strlen(str); char *estr = (char *)str; uint_t tkns; /* * Expand any configuration string. */ if ((tkns = expand(&estr, &size, 0, 0, (PD_TKN_ISALIST | PD_TKN_HWCAP), lmp)) == 0) return (0); /* * If this is a secure application, validate the configuration * file path name. Ignore any untrustworthy path name, and * fall through to pick up the defaults. */ if ((rtld_flags & RT_FL_SECURE) && (is_path_secure(estr, lmp, PD_FLG_FULLPATH, tkns) == 0)) str = NULL; else str = (const char *)estr; } /* * If a configuration file has not been specified try opening up the * default. */ if (str == NULL) { #if defined(_ELF64) str = MSG_ORIG(MSG_PTH_CONFIG_64); #else str = MSG_ORIG(MSG_PTH_CONFIG); #endif } config->c_name = str; /* * If we can't open the configuration file return silently. */ if ((fd = open(str, O_RDONLY, 0)) == -1) return (DBG_CONF_PRCFAIL); /* * Determine the configuration file size and map the file. */ (void) rtld_fstat(fd, &status); if (status.st_size < sizeof (Rtc_head)) { (void) close(fd); return (DBG_CONF_CORRUPT); } if ((addr = (Addr)mmap(0, status.st_size, PROT_READ, MAP_SHARED, fd, 0)) == (Addr)MAP_FAILED) { (void) close(fd); return (DBG_CONF_PRCFAIL); } (void) close(fd); /* * If we have an Rtc_id block at the beginning, then validate it * and advance the address to the Rtc_head. If not, then trust * that the file is compatible with us and move ahead (there is * some error checking for Rtc_head below as well). */ id = (Rtc_id *) addr; if (RTC_ID_TEST(id)) { addr += sizeof (*id); status.st_size -= sizeof (*id); if (status.st_size < sizeof (Rtc_head)) return (DBG_CONF_CORRUPT); if ((id->id_class != M_CLASS) || (id->id_data != M_DATA) || (id->id_machine != M_MACH)) return (DBG_CONF_ABIMISMATCH); } config->c_bgn = addr; config->c_end = addr + status.st_size; head = (Rtc_head *)addr; /* * Make sure we can handle this version of the configuration file. */ if (head->ch_version > RTC_VER_CURRENT) return (DBG_CONF_VERSION); /* * When crle(1) creates a temporary configuration file the * RTC_HDR_IGNORE flag is set. Thus the mapping of the configuration * file is taken into account but not its content. */ if (head->ch_cnflags & RTC_HDR_IGNORE) return (DBG_CONF_IGNORE); /* * Apply any new default library pathname. */ if (head->ch_edlibpath) { str = (const char *)(head->ch_edlibpath + addr); #ifndef SGS_PRE_UNIFIED_PROCESS if ((head->ch_cnflags & RTC_HDR_UPM) == 0) { #if defined(_ELF64) str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIB_64), MSG_ORIG(MSG_PTH_LIB_64), MSG_PTH_LIB_64_SIZE); #else str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIB), MSG_ORIG(MSG_PTH_LIB), MSG_PTH_LIB_SIZE); #endif } #endif if (expand_paths(lmp, str, &elf_def_dirs, AL_CNT_SEARCH, (LA_SER_DEFAULT | LA_SER_CONFIG), PD_TKN_HWCAP) != NULL) features |= CONF_EDLIBPATH; } if (head->ch_eslibpath) { str = (const char *)(head->ch_eslibpath + addr); #ifndef SGS_PRE_UNIFIED_PROCESS if ((head->ch_cnflags & RTC_HDR_UPM) == 0) { #if defined(_ELF64) str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIBSE_64), MSG_ORIG(MSG_PTH_LIBSE_64), MSG_PTH_LIBSE_64_SIZE); #else str = conv_config_upm(str, MSG_ORIG(MSG_PTH_USRLIBSE), MSG_ORIG(MSG_PTH_LIBSE), MSG_PTH_LIBSE_SIZE); #endif } #endif if (expand_paths(lmp, str, &elf_sec_dirs, AL_CNT_SEARCH, (LA_SER_SECURE | LA_SER_CONFIG), PD_TKN_HWCAP) != NULL) features |= CONF_ESLIBPATH; } #if defined(__sparc) && !defined(_ELF64) if (head->ch_adlibpath) { str = (const char *)(head->ch_adlibpath + addr); if (expand_paths(lmp, str, &aout_def_dirs, AL_CNT_SEARCH, (LA_SER_DEFAULT | LA_SER_CONFIG), PD_TKN_HWCAP) != NULL) features |= CONF_ADLIBPATH; } if (head->ch_aslibpath) { str = (const char *)(head->ch_aslibpath + addr); if (expand_paths(lmp, str, &aout_sec_dirs, AL_CNT_SEARCH, (LA_SER_SECURE | LA_SER_CONFIG), PD_TKN_HWCAP) != NULL) features |= CONF_ASLIBPATH; } #endif /* * Apply any environment variables. This attribute was added with * RTC_VER_THREE. */ if ((head->ch_version >= RTC_VER_THREE) && head->ch_env && (!(rtld_flags & RT_FL_NOENVCFG))) { if (readenv_config((Rtc_env *)(head->ch_env + addr), addr, aout) != 0) return (-1); features |= CONF_ENVS; } /* * Determine whether filter/filtee associations are available. */ if ((head->ch_version >= RTC_VER_FOUR) && head->ch_fltr && (!(rtld_flags2 & RT_FL2_NOFLTCFG))) { rtld_flags2 |= RT_FL2_FLTCFG; config->c_fltr = (Rtc_fltr *)(head->ch_fltr + addr); config->c_flte = (Rtc_flte *)(head->ch_flte + addr); features |= CONF_FLTR; } /* * Determine whether directory configuration is available. */ if ((!(rtld_flags & RT_FL_NODIRCFG)) && head->ch_hash) { config->c_hashtbl = (Word *)(head->ch_hash + addr); config->c_hashchain = &config->c_hashtbl[2 + config->c_hashtbl[0]]; config->c_objtbl = (Rtc_obj *)(head->ch_obj + addr); config->c_strtbl = (const char *)(head->ch_str + addr); rtld_flags |= RT_FL_DIRCFG; features |= CONF_DIRCFG; } /* * Determine whether alternative objects are specified or an object * reservation area is required. If the reservation can't be completed * (either because the configuration information is out-of-date, or the * the reservation can't be allocated), then alternative objects are * ignored. */ if ((!(rtld_flags & (RT_FL_NODIRCFG | RT_FL_NOOBJALT))) && (head->ch_cnflags & RTC_HDR_ALTER)) { rtld_flags |= RT_FL_OBJALT; features |= CONF_OBJALT; elf_config_validate(addr, head, lmp); if (head->ch_resbgn) { if (((config->c_bgn <= head->ch_resbgn) && (config->c_bgn >= head->ch_resend)) || (nu_map(LIST(lmp), (caddr_t)(uintptr_t)head->ch_resbgn, (head->ch_resend - head->ch_resbgn), PROT_NONE, MAP_FIXED | MAP_PRIVATE) == MAP_FAILED)) return (-1); rtld_flags |= RT_FL_MEMRESV; features |= CONF_MEMRESV; } } return (features); } /* * Determine whether the given file exists in the configuration file. */ Rtc_obj * elf_config_ent(const char *name, Word hash, int id, const char **alternate) { Word bkt, ndx; const char *str; Rtc_obj *obj; bkt = hash % config->c_hashtbl[0]; ndx = config->c_hashtbl[2 + bkt]; while (ndx) { obj = config->c_objtbl + ndx; str = config->c_strtbl + obj->co_name; if ((obj->co_hash != hash) || (strcmp(name, str) != 0) || (id && (id != obj->co_id))) { ndx = config->c_hashchain[ndx]; continue; } if ((obj->co_flags & RTC_OBJ_ALTER) && alternate) *alternate = config->c_strtbl + obj->co_alter; return (obj); } return (0); } /* * Determine whether a filter and filtee string pair exists in the configuration * file. If so, return the cached filtees that are associated with this pair as * an Alist. */ void elf_config_flt(Lm_list *lml, const char *filter, const char *string, Alist **alpp, Aliste alni) { Rtc_fltr *fltrtbl; for (fltrtbl = (Rtc_fltr *)config->c_fltr; fltrtbl->fr_filter; fltrtbl++) { Rtc_flte *fltetbl; const char *fltr, *str; fltr = config->c_strtbl + fltrtbl->fr_filter; str = config->c_strtbl + fltrtbl->fr_string; if (strcmp(filter, fltr) || strcmp(string, str)) continue; /* * Create a path descriptor for each filtee associated with this * filter/filtee string pair. Note, no expansion of filtee * entries is called for, as any original expansion would have * been carried out before they were recorded in the * configuration file. */ /* LINTED */ for (fltetbl = (Rtc_flte *)((char *)config->c_flte + fltrtbl->fr_filtee); fltetbl->fe_filtee; fltetbl++) { const char *flte; Pdesc *pdp; flte = config->c_strtbl + fltetbl->fe_filtee; if ((pdp = alist_append(alpp, 0, sizeof (Pdesc), alni)) == NULL) return; pdp->pd_pname = (char *)flte; pdp->pd_plen = strlen(flte) + 1; pdp->pd_flags = LA_SER_CONFIG; DBG_CALL(Dbg_file_filter(lml, fltr, flte, 1)); } } }