/*
* 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
* 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) 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
{
AL_CNT_SPATH)) == NULL)
return;
sdp++;
}
}
static void
{
int num = 0;
if (search)
if (search) {
const char *fmt;
if (num++)
else
} else
}
if (search) {
if (flags & LA_SER_CONFIG)
else
}
}
/*
* Given a search rule type, return a list of directories to search according
* to the specified rule.
*/
static Alist **
{
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.
*/
((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) {
/*
* 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;
/*
* 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)
else
}
(search || DBG_ENABLED))
if (rpl_libdirs == NULL) {
/*
* If this is a secure application we need to
* be selective over what directories we use.
*/
}
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) {
/*
* 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)
(search || DBG_ENABLED))
if (prm_libdirs == NULL) {
/*
* If this is a secure application we need to
* be selective over what directories we use.
*/
}
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().
*/
/*
* 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)
(search || DBG_ENABLED))
/*
* If this is a secure application we need to
* be selective over what directories we use.
*/
}
}
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 ((rtld_flags & RT_FL_SECURE) &&
((flags & FLG_RT_PRELOAD) ||
FL1_RT_DEPAUD))))
else
}
/*
* For ldd(1) -s, indicate the default paths that'll be used.
*/
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 *
{
/*
* Make sure there are still rules to process.
*/
/*
* 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 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.
*/
}
/*
* 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.
*/
{
int isaflag = 0;
/*
* 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) &&
flags |= TKN_DOTSLASH;
continue;
}
/*
* Copy any string we've presently passed over to the new
* buffer.
*/
} else {
oname);
return (0);
}
}
/*
* Skip the token delimiter and determine if a reserved token
* match is found.
*/
_flags = 0;
token = 0;
MSG_TKN_ORIGIN_SIZE) == 0) {
/*
* $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) &&
_flags |= PD_TKN_ORIGIN;
} else {
return (0);
}
}
MSG_TKN_PLATFORM_SIZE) == 0) {
else
/*
* $PLATFORM expansion required.
*/
if (((omit & PD_TKN_PLATFORM) == 0) &&
if (((omit & PD_TKN_PLATFORM) == 0) &&
} else {
return (0);
}
}
MSG_TKN_MACHINE_SIZE) == 0) {
else
/*
* $MACHINE expansion required.
*/
if (((omit & PD_TKN_MACHINE) == 0) &&
if (((omit & PD_TKN_MACHINE) == 0) &&
_flags |= PD_TKN_MACHINE;
} else {
return (0);
}
}
MSG_TKN_OSNAME_SIZE) == 0) {
/*
* $OSNAME expansion required. This is established
* from the sysname[] returned by uname(2).
*/
if (((omit & PD_TKN_OSNAME) == 0) &&
uts->uts_osnamesz);
_flags |= PD_TKN_OSNAME;
} else {
return (0);
}
}
MSG_TKN_OSREL_SIZE) == 0) {
/*
* $OSREL expansion required. This is established
* from the release[] returned by uname(2).
*/
if (((omit & PD_TKN_OSREL) == 0) &&
uts->uts_osrelsz);
_flags |= PD_TKN_OSREL;
} else {
return (0);
}
}
MSG_TKN_ISALIST_SIZE) == 0)) {
int ok;
/*
* $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.
*/
ok = 0;
else
ok = 1;
isa = conv_isalist();
char *lptr;
opt->isa_namesz);
_flags |= PD_TKN_ISALIST;
} else {
return (0);
}
if (list) {
return (0);
hlen);
opt->isa_namesz);
tlen);
*lptr++ = ':';
}
if (**list)
else
*--lptr = '\0';
}
}
MSG_TKN_CAPABILITY_SIZE) == 0) {
/*
* $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) &&
/*
* Decrement the present pointer so that the
* directories trailing "/" gets nuked later.
*/
_flags |= PD_TKN_CAP;
}
MSG_TKN_HWCAP_SIZE) == 0) {
/*
* $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) &&
/*
* Decrement the present pointer so that the
* directories trailing "/" gets nuked later.
*/
_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)
else {
/*
* 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.
*/
} else
return (0);
}
}
}
/*
* 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.
*/
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 (isaflag == 1) {
if (list)
} else {
flags &= ~PD_TKN_ISALIST;
return (0);
return (TKN_NONE);
}
}
/*
* Copy any remaining string. Terminate the new string with a null as
* this string can be displayed via debugging diagnostics.
*/
} else {
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.
*/
int len;
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.
*/
return (0);
}
/*
* Determine whether a path name is secure.
*/
int
{
/*
* 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.
*/
/*
* 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
* . provided $ORIGIN expansion has not been employed, the
* above categories of path are deemed secure.
*/
((info & PD_FLG_EXTLOAD) == 0))) &&
((flags & PD_TKN_ORIGIN) == 0))
return (1);
/*
* Determine the directory name of the present path.
*/
if (str) {
else {
return (0);
}
/*
* 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) &&
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.
*/
((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) {
return (1);
return (1);
}
}
}
/*
* Determine whether the present directory is trusted.
*/
if (npath) {
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) {
opath);
} else
opath);
return (0);
}
/*
* Explicit file references are fatal.
*/
if ((info & LA_SER_MASK) == 0) {
/* BEGIN CSTYLED */
(void) printf(
if (((rtld_flags & RT_FL_SILENCERR) == 0) ||
(void) printf(
opath);
}
/* END CSTYLED */
} else
} else {
/*
* Search paths.
*/
}
return (0);
}
/*
* Determine whether a path already exists within the callers Pnode list.
*/
inline static uint_t
{
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
{
char *ostr;
if (*nlist == ';')
/* If not a final null segment, check following one */
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.
*/
continue; /* Process next segment */
} else {
len = 0;
if (*nlist == '/')
tkns |= PD_FLG_PNSLASH;
}
/* Check for a following final null segment */
if (*nlist)
nlist++;
/*
* Expand the captured string. Besides expanding the
* deal with (ISALIST expands to multiple new entries).
*/
clmp)) == 0)
continue;
}
/*
* If this a secure application, validation of the expanded
* path name may be necessary.
*/
if ((rtld_flags & RT_FL_SECURE) &&
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) {
/*
* 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.
*/
continue;
}
/*
* Create a new pathname descriptor.
*/
return (0);
/*
* 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.
*/
return (0);
/*
* Now that any duplication of the original string has occurred,
* release any previous old listing.
*/
if (olist)
}
}
if (olist)
/*
* If no paths could be determined (perhaps because of security), then
* indicate a failure.
*/
}
/*
* 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.
*/
{
const char *name;
/*
* Determine whether this path name is already 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.
*/
else
} else {
/*
* If this path name has not yet been resolved, resolve the
* current name.
*/
const char *path;
else
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.
*/
}
}
/*
* See if the path name can be reduced further.
*/
}
/*
* 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.
*/
}
/*
* Establish the directory name size - this also acts as a flag that the
* directory name has been computed.
*/
}