/*
* 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 <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <string.h>
#include <debug.h>
#include <conv.h>
#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));
}