/* * 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 (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Copyright (c) 1988 AT&T * All Rights Reserved */ /* * Copyright (c) 2012, Joyent, Inc. All rights reserved. */ /* * PATH setup and search directory functions. */ #include #include #include #include #include #include #include #include "_rtld.h" #include "msg.h" /* * Default and secure dependency search path initialization. */ void set_dirs(Alist **alpp, Spath_defn *sdp, uint_t flags) { while (sdp->sd_name) { Pdesc *pdp; if ((pdp = alist_append(alpp, NULL, sizeof (Pdesc), AL_CNT_SPATH)) == NULL) return; pdp->pd_pname = (char *)sdp->sd_name; pdp->pd_plen = sdp->sd_len; pdp->pd_flags = flags; sdp++; } } static void print_default_dirs(Lm_list *lml, Alist *alp, int search) { uint_t flags = 0; int num = 0; Aliste idx; Pdesc *pdp; if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_BGNDFL)); for (ALIST_TRAVERSE(alp, idx, pdp)) { flags = pdp->pd_flags; if (search) { const char *fmt; if (num++) fmt = MSG_ORIG(MSG_LDD_FMT_PATHN); else fmt = MSG_ORIG(MSG_LDD_FMT_PATH1); (void) printf(fmt, pdp->pd_pname); } else DBG_CALL(Dbg_libs_path(lml, pdp->pd_pname, pdp->pd_flags, config->c_name)); } if (search) { if (flags & LA_SER_CONFIG) (void) printf(MSG_INTL(MSG_LDD_PTH_ENDDFLC), config->c_name); else (void) printf(MSG_INTL(MSG_LDD_PTH_ENDDFL)); } } /* * Given a search rule type, return a list of directories to search according * to the specified rule. */ static Alist ** get_dir_list(uchar_t rules, Rt_map *lmp, uint_t flags) { Alist **dalpp = NULL; Lm_list *lml = LIST(lmp); int search; /* * Determine whether ldd -s is in effect - ignore when we're searching * for audit libraries as these will be added to their own link-map. */ if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(lmp) & FL1_RT_LDDSTUB) == 0) && ((flags & FLG_RT_AUDIT) == 0)) search = 1; else search = 0; switch (rules) { case RPLENV: /* * Initialize the replaceable environment variable * (LD_LIBRARY_PATH) search path list. Note, we always call * Dbg_libs_path() so that every library lookup diagnostic can * be preceded with the appropriate search path information. */ if (rpl_libpath) { uint_t mode = (LA_SER_LIBPATH | PD_FLG_UNIQUE); /* * Note, this path may have originated from the users * environment or from a configuration file. */ if (env_info & ENV_INF_PATHCFG) mode |= LA_SER_CONFIG; DBG_CALL(Dbg_libs_path(lml, rpl_libpath, mode, config->c_name)); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure application then some * search paths may be ignored, therefore reset the * rpl_libdirs pointer each time so that the * diagnostics related to these unsecure directories * will be output for each image loaded. */ if (search) { const char *fmt; if (env_info & ENV_INF_PATHCFG) fmt = MSG_INTL(MSG_LDD_PTH_LIBPATHC); else fmt = MSG_INTL(MSG_LDD_PTH_LIBPATH); (void) printf(fmt, rpl_libpath, config->c_name); } if (rpl_libdirs && (rtld_flags & RT_FL_SECURE) && (search || DBG_ENABLED)) remove_alist(&rpl_libdirs, 1); if (rpl_libdirs == NULL) { /* * If this is a secure application we need to * be selective over what directories we use. */ (void) expand_paths(lmp, rpl_libpath, &rpl_libdirs, AL_CNT_SEARCH, mode, PD_TKN_CAP); } dalpp = &rpl_libdirs; } break; case PRMENV: /* * Initialize the permanent (LD_LIBRARY_PATH) search path list. * This can only originate from a configuration file. To be * consistent with the debugging display of DEFENV (above), * always call Dbg_libs_path(). */ if (prm_libpath) { uint_t mode = (LA_SER_LIBPATH | LA_SER_CONFIG | PD_FLG_UNIQUE); DBG_CALL(Dbg_libs_path(lml, prm_libpath, mode, config->c_name)); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure application then some * search paths may be ignored, therefore reset the * prm_libdirs pointer each time so that the * diagnostics related to these unsecure directories * will be output for each image loaded. */ if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_LIBPATHC), prm_libpath, config->c_name); if (prm_libdirs && (rtld_flags & RT_FL_SECURE) && (search || DBG_ENABLED)) remove_alist(&prm_libdirs, 1); if (prm_libdirs == NULL) { /* * If this is a secure application we need to * be selective over what directories we use. */ (void) expand_paths(lmp, prm_libpath, &prm_libdirs, AL_CNT_SEARCH, mode, PD_TKN_CAP); } dalpp = &prm_libdirs; } break; case RUNPATH: /* * Initialize the runpath search path list. To be consistent * with the debugging display of DEFENV (above), always call * Dbg_libs_path(). */ if (RPATH(lmp)) { DBG_CALL(Dbg_libs_path(lml, RPATH(lmp), LA_SER_RUNPATH, NAME(lmp))); /* * For ldd(1) -s, indicate the search paths that'll * be used. If this is a secure application then some * search paths may be ignored, therefore reset the * runlist pointer each time so that the diagnostics * related to these unsecure directories will be * output for each image loaded. */ if (search) (void) printf(MSG_INTL(MSG_LDD_PTH_RUNPATH), RPATH(lmp), NAME(lmp)); if (RLIST(lmp) && (rtld_flags & RT_FL_SECURE) && (search || DBG_ENABLED)) remove_alist(&RLIST(lmp), 1); if (RLIST(lmp) == NULL) { /* * If this is a secure application we need to * be selective over what directories we use. */ (void) expand_paths(lmp, RPATH(lmp), &RLIST(lmp), AL_CNT_SEARCH, LA_SER_RUNPATH, PD_TKN_CAP); } dalpp = &RLIST(lmp); } break; case DEFAULT: /* * If we have been requested to load an audit library through a * DT_DEPAUDIT entry, then we treat this the same way that we * handle a library that has been specified via a DT_NEEDED * entry -- we check the default directories and not the * secure directories. */ if ((FLAGS1(lmp) & FL1_RT_NODEFLIB) == 0) { if ((rtld_flags & RT_FL_SECURE) && ((flags & FLG_RT_PRELOAD) || ((flags & FLG_RT_AUDIT) && !(FLAGS1(lmp) & FL1_RT_DEPAUD)))) dalpp = LM_SECURE_DIRS(lmp)(); else dalpp = LM_DEFAULT_DIRS(lmp)(); } /* * For ldd(1) -s, indicate the default paths that'll be used. */ if (dalpp && (search || DBG_ENABLED)) print_default_dirs(lml, *dalpp, search); break; default: break; } return (dalpp); } /* * Get the next directory in the search rules path. The search path "cookie" * provided by the caller (sdp) maintains the state of a search in progress. * * Typically, a search consists of a series of rules that govern the order of * a search (ie. LD_LIBRARY_PATH, followed by RPATHS, followed by defaults). * Each rule can establish a corresponding series of path names, which are * maintained as an Alist. The index within this Alist determines the present * search directory. */ Pdesc * get_next_dir(Spath_desc *sdp, Rt_map *lmp, uint_t flags) { /* * Make sure there are still rules to process. */ while (*sdp->sp_rule) { Alist *alp; /* * If an Alist for this rule already exists, use if, otherwise * obtain an Alist for this rule. Providing the Alist has * content, and the present Alist index is less than the number * of Alist members, return the associated path name descriptor. */ if ((sdp->sp_dalpp || ((sdp->sp_dalpp = get_dir_list(*sdp->sp_rule, lmp, flags)) != NULL)) && ((alp = *sdp->sp_dalpp) != NULL) && (alist_nitems(alp) > sdp->sp_idx)) { return (alist_item(alp, sdp->sp_idx++)); } /* * If no Alist for this rule exists, or if this is the last * element of this Alist, reset the Alist pointer and index, * and prepare for the next rule. */ sdp->sp_rule++; sdp->sp_dalpp = NULL; sdp->sp_idx = 0; } /* * All rules and search paths have been exhausted. */ return (NULL); } /* * Process a directory (runpath) or filename (needed or filter) string looking * for tokens to expand. Allocate a new buffer for the string. */ uint_t expand(char **name, size_t *len, char **list, uint_t orig, uint_t omit, Rt_map *lmp) { char _name[PATH_MAX]; char *token = NULL, *oname, *ename, *optr, *_optr, *nptr, *_list; size_t olen = 0, nlen = 0, _len; int isaflag = 0; uint_t flags = 0; Lm_list *lml = LIST(lmp); optr = _optr = oname = ename = *name; ename += *len; nptr = _name; while ((olen < *len) && (nlen < PATH_MAX)) { uint_t _flags; if ((*optr != '$') || ((olen - *len) == 1)) { /* * When expanding paths while a configuration file * exists that contains directory information, determine * whether the path contains "./". If so, we'll resolve * the path later to remove these relative entries. */ if ((rtld_flags & RT_FL_DIRCFG) && (orig & LA_SER_MASK) && (*optr == '/') && (optr != oname) && (*(optr - 1) == '.')) flags |= TKN_DOTSLASH; olen++, optr++; continue; } /* * Copy any string we've presently passed over to the new * buffer. */ if ((_len = (optr - _optr)) != 0) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, _optr, _len); nptr = nptr + _len; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } /* * Skip the token delimiter and determine if a reserved token * match is found. */ olen++, optr++; _flags = 0; token = 0; if (strncmp(optr, MSG_ORIG(MSG_TKN_ORIGIN), MSG_TKN_ORIGIN_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_ORIGIN); /* * $ORIGIN expansion is required. Determine this * objects basename. Expansion of $ORIGIN is allowed * for secure applications but must be checked by the * caller to insure the expanded path matches a * registered secure name. */ if (((omit & PD_TKN_ORIGIN) == 0) && (((_len = DIRSZ(lmp)) != 0) || ((_len = fullpath(lmp, 0)) != 0))) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, ORIGNAME(lmp), _len); nptr = nptr +_len; olen += MSG_TKN_ORIGIN_SIZE; optr += MSG_TKN_ORIGIN_SIZE; _flags |= PD_TKN_ORIGIN; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_PLATFORM), MSG_TKN_PLATFORM_SIZE) == 0) { Syscapset *scapset; if (FLAGS1(lmp) & FL1_RT_ALTCAP) scapset = alt_scapset; else scapset = org_scapset; token = (char *)MSG_ORIG(MSG_TKN_PLATFORM); /* * $PLATFORM expansion required. */ if (((omit & PD_TKN_PLATFORM) == 0) && ((scapset->sc_plat == NULL) && (scapset->sc_platsz == 0))) platform_name(scapset); if (((omit & PD_TKN_PLATFORM) == 0) && scapset->sc_plat) { nlen += scapset->sc_platsz; if (nlen < PATH_MAX) { (void) strncpy(nptr, scapset->sc_plat, scapset->sc_platsz); nptr = nptr + scapset->sc_platsz; olen += MSG_TKN_PLATFORM_SIZE; optr += MSG_TKN_PLATFORM_SIZE; _flags |= PD_TKN_PLATFORM; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_MACHINE), MSG_TKN_MACHINE_SIZE) == 0) { Syscapset *scapset; if (FLAGS1(lmp) & FL1_RT_ALTCAP) scapset = alt_scapset; else scapset = org_scapset; token = (char *)MSG_ORIG(MSG_TKN_MACHINE); /* * $MACHINE expansion required. */ if (((omit & PD_TKN_MACHINE) == 0) && ((scapset->sc_mach == NULL) && (scapset->sc_machsz == 0))) machine_name(scapset); if (((omit & PD_TKN_MACHINE) == 0) && scapset->sc_mach) { nlen += scapset->sc_machsz; if (nlen < PATH_MAX) { (void) strncpy(nptr, scapset->sc_mach, scapset->sc_machsz); nptr = nptr + scapset->sc_machsz; olen += MSG_TKN_MACHINE_SIZE; optr += MSG_TKN_MACHINE_SIZE; _flags |= PD_TKN_MACHINE; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_OSNAME), MSG_TKN_OSNAME_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_OSNAME); /* * $OSNAME expansion required. This is established * from the sysname[] returned by uname(2). */ if (((omit & PD_TKN_OSNAME) == 0) && (uts == NULL)) uts = conv_uts(); if (((omit & PD_TKN_OSNAME) == 0) && (uts && uts->uts_osnamesz)) { if ((nlen += uts->uts_osnamesz) < PATH_MAX) { (void) strncpy(nptr, uts->uts_osname, uts->uts_osnamesz); nptr = nptr + uts->uts_osnamesz; olen += MSG_TKN_OSNAME_SIZE; optr += MSG_TKN_OSNAME_SIZE; _flags |= PD_TKN_OSNAME; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_OSREL), MSG_TKN_OSREL_SIZE) == 0) { token = (char *)MSG_ORIG(MSG_TKN_OSREL); /* * $OSREL expansion required. This is established * from the release[] returned by uname(2). */ if (((omit & PD_TKN_OSREL) == 0) && (uts == 0)) uts = conv_uts(); if (((omit & PD_TKN_OSREL) == 0) && (uts && uts->uts_osrelsz)) { if ((nlen += uts->uts_osrelsz) < PATH_MAX) { (void) strncpy(nptr, uts->uts_osrel, uts->uts_osrelsz); nptr = nptr + uts->uts_osrelsz; olen += MSG_TKN_OSREL_SIZE; optr += MSG_TKN_OSREL_SIZE; _flags |= PD_TKN_OSREL; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } } else if ((strncmp(optr, MSG_ORIG(MSG_TKN_ISALIST), MSG_TKN_ISALIST_SIZE) == 0)) { int ok; token = (char *)MSG_ORIG(MSG_TKN_ISALIST); /* * $ISALIST expansion required. When accompanied with * a list pointer, this routine updates that pointer * with the new list of potential candidates. Without * this list pointer, only the first expansion is * provided. NOTE, that two $ISLIST expansions within * the same path aren't supported. */ if ((omit & PD_TKN_ISALIST) || isaflag++) ok = 0; else ok = 1; if (ok && (isa == NULL)) isa = conv_isalist(); if (ok && isa && isa->isa_listsz) { size_t no, mlen, tlen, hlen = olen - 1; char *lptr; Isa_opt *opt = isa->isa_opt; if ((nlen += opt->isa_namesz) < PATH_MAX) { (void) strncpy(nptr, opt->isa_name, opt->isa_namesz); nptr = nptr + opt->isa_namesz; olen += MSG_TKN_ISALIST_SIZE; optr += MSG_TKN_ISALIST_SIZE; _flags |= PD_TKN_ISALIST; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } if (list) { tlen = *len - olen; mlen = ((hlen + tlen) * (isa->isa_optno - 1)) + isa->isa_listsz - opt->isa_namesz + strlen(*list); if ((_list = lptr = malloc(mlen)) == NULL) return (0); for (no = 1, opt++; no < isa->isa_optno; no++, opt++) { (void) strncpy(lptr, *name, hlen); lptr = lptr + hlen; (void) strncpy(lptr, opt->isa_name, opt->isa_namesz); lptr = lptr + opt->isa_namesz; (void) strncpy(lptr, optr, tlen); lptr = lptr + tlen; *lptr++ = ':'; } if (**list) (void) strcpy(lptr, *list); else *--lptr = '\0'; } } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_CAPABILITY), MSG_TKN_CAPABILITY_SIZE) == 0) { char *bptr = nptr - 1; char *eptr = optr + MSG_TKN_CAPABILITY_SIZE; token = (char *)MSG_ORIG(MSG_TKN_CAPABILITY); /* * $CAPABILITY expansion required. Expansion is only * allowed for non-simple path names (must contain a * '/'), with the token itself being the last element * of the path. Therefore, all we need do is test the * existence of the string "/$CAPABILITY\0". */ if (((omit & PD_TKN_CAP) == 0) && ((bptr > _name) && (*bptr == '/') && ((*eptr == '\0') || (*eptr == ':')))) { /* * Decrement the present pointer so that the * directories trailing "/" gets nuked later. */ nptr--, nlen--; olen += MSG_TKN_CAPABILITY_SIZE; optr += MSG_TKN_CAPABILITY_SIZE; _flags |= PD_TKN_CAP; } } else if (strncmp(optr, MSG_ORIG(MSG_TKN_HWCAP), MSG_TKN_HWCAP_SIZE) == 0) { char *bptr = nptr - 1; char *eptr = optr + MSG_TKN_HWCAP_SIZE; token = (char *)MSG_ORIG(MSG_TKN_HWCAP); /* * $HWCAP expansion required. This token has been * superseeded by $CAPABILITY. For compatibility with * older environments, only expand this token when hard- * ware capability information is available. This * expansion is only allowed for non-simple path names * (must contain a '/'), with the token itself being the * last element of the path. Therefore, all we need do * is test the existence of the string "/$HWCAP\0". */ if (((omit & PD_TKN_CAP) == 0) && (rtld_flags2 & RT_FL2_HWCAP) && ((bptr > _name) && (*bptr == '/') && ((*eptr == '\0') || (*eptr == ':')))) { /* * Decrement the present pointer so that the * directories trailing "/" gets nuked later. */ nptr--, nlen--; olen += MSG_TKN_HWCAP_SIZE; optr += MSG_TKN_HWCAP_SIZE; _flags |= PD_TKN_CAP; } } else { /* * If reserved token was not found, copy the * character. */ *nptr++ = '$'; nlen++; } /* * If a reserved token was found, and could not be expanded, * diagnose the error condition. */ if (token) { if (_flags) flags |= _flags; else { char buf[PATH_MAX], *str; /* * Note, the original string we're expanding * might contain a number of ':' separated * paths. Isolate the path we're processing to * provide a more precise error diagnostic. */ if (str = strchr(oname, ':')) { size_t slen = str - oname; (void) strncpy(buf, oname, slen); buf[slen] = '\0'; str = buf; } else str = oname; eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND2), NAME(lmp), str, token); return (0); } } _optr = optr; } /* * First make sure the current length is shorter than PATH_MAX. We may * arrive here if the given path contains '$' characters which are not * the lead of a reserved token. */ if (nlen >= PATH_MAX) { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } /* * If any ISALIST processing has occurred not only do we return the * expanded node we're presently working on, but we can also update the * remaining list so that it is effectively prepended with this node * expanded to all remaining ISALIST options. Note that we can only * handle one ISALIST per node. For more than one ISALIST to be * processed we'd need a better algorithm than above to replace the * newly generated list. Whether we want to encourage the number of * path name permutations this would provide is another question. So, * for now if more than one ISALIST is encountered we return the * original node untouched. */ if (isa && isaflag) { if (isaflag == 1) { if (list) *list = _list; } else { flags &= ~PD_TKN_ISALIST; if ((nptr = (char *)stravl_insert(*name, 0, (*len + 1), 1)) == NULL) return (0); *name = nptr; return (TKN_NONE); } } /* * Copy any remaining string. Terminate the new string with a null as * this string can be displayed via debugging diagnostics. */ if ((_len = (optr - _optr)) != 0) { if ((nlen += _len) < PATH_MAX) { (void) strncpy(nptr, _optr, _len); nptr = nptr + _len; } else { eprintf(lml, ERR_FATAL, MSG_INTL(MSG_ERR_EXPAND1), NAME(lmp), oname); return (0); } } *nptr = '\0'; /* * A path that has been expanded is typically used to create full * path names for objects that will be opened. The final path name is * resolved to simplify it, and set the stage for possible $ORIGIN * processing. Therefore, it's usually unnecessary to resolve the path * at this point. However, if a configuration file, containing * directory information is in use, then we might need to lookup this * path in the configuration file. To keep the number of path name * resolutions to a minimum, only resolve paths that contain "./". The * use of "$ORIGIN/../lib" will probably only match a configuration file * entry after resolution. */ if (list && (rtld_flags & RT_FL_DIRCFG) && (flags & TKN_DOTSLASH)) { int len; if ((len = resolvepath(_name, _name, (PATH_MAX - 1))) >= 0) { nlen = (size_t)len; _name[nlen] = '\0'; flags |= PD_TKN_RESOLVED; } } /* * Allocate a new string if necessary. * * If any form of token expansion, or string resolution has occurred, * the storage must be allocated for the new string. * * If we're processing a substring, for example, any string besides the * last string within a search path "A:B:C", then this substring needs * to be isolated with a null terminator. However, if this search path * was created from a previous ISALIST expansion, then all strings must * be allocated, as the isalist expansion will be freed after expansion * processing. */ if ((nptr = (char *)stravl_insert(_name, 0, (nlen + 1), 1)) == NULL) return (0); *name = nptr; *len = nlen; return (flags ? flags : TKN_NONE); } /* * Determine whether a path name is secure. */ int is_path_secure(char *opath, Rt_map *clmp, uint_t info, uint_t flags) { Alist **salpp; Aliste idx; char buffer[PATH_MAX], *npath = NULL; Lm_list *lml = LIST(clmp); Pdesc *pdp; /* * If a path name originates from a configuration file, use it. The use * of a configuration file is already validated for secure applications, * so if we're using a configuration file, we must be able to use all * that it defines. */ if (info & LA_SER_CONFIG) return (1); if ((info & LA_SER_MASK) == 0) { char *str; /* * If the path name specifies a file (rather than a directory), * peel off the file before making the comparison. */ str = strrchr(opath, '/'); /* * Carry out some initial security checks. * * . a simple file name (one containing no "/") is fine, as * this file name will be combined with search paths to * determine the complete path. Note, a secure application * may provide a configuration file, and this can only be * a full path name (PN_FLG_FULLPATH). * . a full path (one starting with "/") is fine, provided * this path name isn't a preload/audit path. * . provided $ORIGIN expansion has not been employed, the * above categories of path are deemed secure. */ if ((((str == 0) && ((info & PD_FLG_FULLPATH) == 0)) || ((*opath == '/') && (str != opath) && ((info & PD_FLG_EXTLOAD) == 0))) && ((flags & PD_TKN_ORIGIN) == 0)) return (1); /* * Determine the directory name of the present path. */ if (str) { if (str == opath) npath = (char *)MSG_ORIG(MSG_STR_SLASH); else { size_t size; if ((size = str - opath) >= PATH_MAX) return (0); (void) strncpy(buffer, opath, size); buffer[size] = '\0'; npath = buffer; } /* * If $ORIGIN processing has been employed, then allow * any directory that has already been used to satisfy * other dependencies, to be used. */ if ((flags & PD_TKN_ORIGIN) && pnavl_recorded(&spavl, npath, 0, NULL)) { DBG_CALL(Dbg_libs_insecure(lml, npath, 1)); return (1); } } } else { /* * A search path, i.e., RPATH, configuration file path, etc. is * used as is. Exceptions to this are: * * . LD_LIBRARY_PATH. * . any $ORIGIN expansion, unless used by a setuid ld.so.1 * to find its own dependencies, or the path name has * already been used to find other dependencies. * . any relative path. */ if (((info & LA_SER_LIBPATH) == 0) && (*opath == '/') && ((flags & PD_TKN_ORIGIN) == 0)) return (1); /* * If $ORIGIN processing is requested, allow a setuid ld.so.1 * to use this path for its own dependencies. Allow the * application to use this path name only if the path name has * already been used to locate other dependencies. */ if (flags & PD_TKN_ORIGIN) { if ((lml->lm_flags & LML_FLG_RTLDLM) && is_rtld_setuid()) return (1); else if (pnavl_recorded(&spavl, opath, 0, NULL)) { DBG_CALL(Dbg_libs_insecure(lml, opath, 1)); return (1); } } npath = (char *)opath; } /* * Determine whether the present directory is trusted. */ if (npath) { salpp = LM_SECURE_DIRS(LIST(clmp)->lm_head)(); for (ALIST_TRAVERSE(*salpp, idx, pdp)) { if (strcmp(npath, pdp->pd_pname) == 0) return (1); } } /* * The path is insecure, so depending on the caller, provide a * diagnostic. Preloaded, or audit libraries generate a warning, as * the process will run without them. */ if (info & PD_FLG_EXTLOAD) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) { if ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0) (void) printf(MSG_INTL(MSG_LDD_FIL_ILLEGAL), opath); } else eprintf(lml, ERR_WARNING, MSG_INTL(MSG_SEC_ILLEGAL), opath); return (0); } /* * Explicit file references are fatal. */ if ((info & LA_SER_MASK) == 0) { if (lml->lm_flags & LML_FLG_TRC_ENABLE) { /* BEGIN CSTYLED */ if ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0) { if (lml->lm_flags & (LML_FLG_TRC_VERBOSE | LML_FLG_TRC_SEARCH)) (void) printf( MSG_INTL(MSG_LDD_FIL_FIND), opath, NAME(clmp)); if (((rtld_flags & RT_FL_SILENCERR) == 0) || (lml->lm_flags & LML_FLG_TRC_VERBOSE)) (void) printf( MSG_INTL(MSG_LDD_FIL_ILLEGAL), opath); } /* END CSTYLED */ } else eprintf(lml, ERR_FATAL, MSG_INTL(MSG_SYS_OPEN), opath, strerror(EACCES)); } else { /* * Search paths. */ DBG_CALL(Dbg_libs_insecure(lml, opath, 0)); if ((lml->lm_flags & LML_FLG_TRC_SEARCH) && ((FLAGS1(clmp) & FL1_RT_LDDSTUB) == 0)) (void) printf(MSG_INTL(MSG_LDD_PTH_IGNORE), opath); } return (0); } /* * Determine whether a path already exists within the callers Pnode list. */ inline static uint_t is_path_unique(Alist *alp, const char *path) { Aliste idx; Pdesc *pdp; for (ALIST_TRAVERSE(alp, idx, pdp)) { if (pdp->pd_plen && (strcmp(pdp->pd_pname, path) == 0)) return (PD_FLG_DUPLICAT); } return (0); } /* * Expand one or more path names. This routine is called for all path strings, * i.e., NEEDED, rpaths, default search paths, configuration file search paths, * filtees, etc. The path may be a single path name, or a colon separated list * of path names. Each individual path name is processed for possible reserved * token expansion. All string nodes are maintained in allocated memory * (regardless of whether they are constant (":"), or token expanded) to * simplify path name descriptor removal. * * The info argument passes in auxiliary information regarding the callers * intended use of the path names. This information may be maintained in the * path name descriptor element produced to describe the path name (i.e., * LA_SER_LIBPATH etc.), or may be used to determine additional security or * diagnostic processing. */ int expand_paths(Rt_map *clmp, const char *list, Alist **alpp, Aliste alni, uint_t orig, uint_t omit) { char *str, *olist = 0, *nlist = (char *)list; int fnull = FALSE; /* TRUE if empty final path segment seen */ Pdesc *pdp = NULL; for (str = nlist; *nlist || fnull; str = nlist) { char *ostr; char *elist = NULL; size_t len, olen; uint_t tkns = 0; if (*nlist == ';') ++nlist, ++str; if ((*nlist == ':') || fnull) { /* If not a final null segment, check following one */ fnull = !(fnull || *(nlist + 1)); if (*nlist) nlist++; /* * When the shell sees a null PATH segment, it * treats it as if it were the cwd (.). We mimic * this behavior for LD_LIBRARY_PATH and runpaths * (mainly for backwards compatibility with previous * behavior). For other paths, this makes no sense, * so we simply ignore the segment. */ if (!(orig & (LA_SER_LIBPATH | LA_SER_RUNPATH))) continue; /* Process next segment */ str = (char *)MSG_ORIG(MSG_FMT_CWD); len = MSG_FMT_CWD_SIZE; } else { uint_t _tkns; len = 0; while (*nlist && (*nlist != ':') && (*nlist != ';')) { if (*nlist == '/') tkns |= PD_FLG_PNSLASH; nlist++, len++; } /* Check for a following final null segment */ fnull = (*nlist == ':') && !*(nlist + 1); if (*nlist) nlist++; /* * Expand the captured string. Besides expanding the * present path/file entry, we may have a new list to * deal with (ISALIST expands to multiple new entries). */ elist = nlist; ostr = str; olen = len; if ((_tkns = expand(&str, &len, &elist, orig, omit, clmp)) == 0) continue; tkns |= _tkns; } /* * If this a secure application, validation of the expanded * path name may be necessary. */ if ((rtld_flags & RT_FL_SECURE) && (is_path_secure(str, clmp, orig, tkns) == 0)) continue; /* * If required, ensure that the string is unique. For search * paths such as LD_LIBRARY_PATH, users often inherit multiple * paths which result in unnecessary duplication. Note, if * we're debugging, any duplicate entry is retained and flagged * so that the entry can be diagnosed later as part of unused * processing. */ if (orig & PD_FLG_UNIQUE) { Word tracing; tracing = LIST(clmp)->lm_flags & (LML_FLG_TRC_UNREF | LML_FLG_TRC_UNUSED); tkns |= is_path_unique(*alpp, str); /* * Note, use the debug strings rpl_debug and prm_debug * as an indicator that debugging has been requested, * rather than DBG_ENABLE(), as the initial use of * LD_LIBRARY_PATH occurs in preparation for loading * our debugging library. */ if ((tkns & PD_FLG_DUPLICAT) && (tracing == 0) && (rpl_debug == 0) && (prm_debug == 0)) continue; } /* * Create a new pathname descriptor. */ if ((pdp = alist_append(alpp, NULL, sizeof (Pdesc), alni)) == NULL) return (0); pdp->pd_pname = str; pdp->pd_plen = len; pdp->pd_flags = (orig & LA_SER_MASK) | (tkns & PD_MSK_INHERIT); /* * If token expansion occurred, maintain the original string. * This string can be used to provide a more informative error * diagnostic for a file that fails to load, or for displaying * unused search paths. */ if ((tkns & PD_MSK_EXPAND) && ((pdp->pd_oname = stravl_insert(ostr, 0, (olen + 1), 1)) == NULL)) return (0); /* * Now that any duplication of the original string has occurred, * release any previous old listing. */ if (elist && (elist != nlist)) { if (olist) free(olist); nlist = olist = elist; } } if (olist) free(olist); /* * If no paths could be determined (perhaps because of security), then * indicate a failure. */ return (pdp != NULL); } /* * Establish an objects fully resolved path. * * When $ORIGIN was first introduced, the expansion of a relative path name was * deferred until it was required. However now we insure a full path name is * always created - things like the analyzer wish to rely on librtld_db * returning a full path. The overhead of this is perceived to be low, * providing the associated libc version of getcwd is available (see 4336878). * This getcwd() was ported back to Solaris 8.1. */ size_t fullpath(Rt_map *lmp, Fdesc *fdp) { const char *name; /* * Determine whether this path name is already resolved. */ if (fdp && (fdp->fd_flags & FLG_FD_RESOLVED)) { /* * If the resolved path differed from the original name, the * resolved path would have been recorded as the fd_pname. * Steal this path name from the file descriptor. Otherwise, * the path name is the same as the name of this object. */ if (fdp->fd_pname) PATHNAME(lmp) = fdp->fd_pname; else PATHNAME(lmp) = NAME(lmp); } else { /* * If this path name has not yet been resolved, resolve the * current name. */ char _path[PATH_MAX]; const char *path; int size, rsize; if (fdp && fdp->fd_pname) PATHNAME(lmp) = fdp->fd_pname; else PATHNAME(lmp) = NAME(lmp); name = path = PATHNAME(lmp); size = strlen(name); if (path[0] != '/') { /* * If we can't determine the current directory (possible * if too many files are open - EMFILE), or if the * created path is too big, simply revert back to the * initial path name. */ if (getcwd(_path, (PATH_MAX - 2 - size)) != NULL) { (void) strcat(_path, MSG_ORIG(MSG_STR_SLASH)); (void) strcat(_path, name); path = _path; size = strlen(path); } } /* * See if the path name can be reduced further. */ if ((rsize = resolvepath(path, _path, (PATH_MAX - 1))) > 0) { _path[rsize] = '\0'; path = _path; size = rsize; } /* * If the path name is different from the original, duplicate it * so that it is available in a core file. If the duplication * fails simply leave the original path name alone. */ if ((PATHNAME(lmp) = stravl_insert(path, 0, (size + 1), 0)) == NULL) PATHNAME(lmp) = name; } name = ORIGNAME(lmp) = PATHNAME(lmp); /* * Establish the directory name size - this also acts as a flag that the * directory name has been computed. */ DIRSZ(lmp) = strrchr(name, '/') - name; return (DIRSZ(lmp)); }