371 lines
9.9 KiB
C
371 lines
9.9 KiB
C
/* @(#)cache_rtld.c 1.1 94/10/31 SMI */
|
|
|
|
/*
|
|
* ld.so directory caching: run-time link-editor specific functions.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 1989, 1991 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include "link.h"
|
|
#include "../ld/dynamic.h"
|
|
#include "rtld.h"
|
|
#include "cache.h"
|
|
#include <string.h>
|
|
|
|
static struct dd *get_next_dir();
|
|
static char *ask_db();
|
|
static char *get_next_name();
|
|
static struct dd *new_dd();
|
|
static secure_directory();
|
|
|
|
static struct dd *ddhead = NULL; /* master head of list pointer */
|
|
static char *es; /* LD_LIBRARY_PATH cache */
|
|
static char *default_dirs[] = /* directories searched by default */
|
|
{"/usr/lib", "/usr/local/lib", '\0'};
|
|
static char *secure_dirs[] = /* directories we trust */
|
|
{"/usr/lib","/usr/local/lib","/usr/5lib", '\0'};
|
|
static char **also_secure = NULL; /* directories contributed by main */
|
|
|
|
extern char *library_path; /* alternate path for library search */
|
|
extern struct link_map *mlmp; /* main program's link map */
|
|
|
|
/*
|
|
* Lookup a canonical link_object, return its absolute pathname (or NULL).
|
|
*/
|
|
char *
|
|
lo_lookup(lop, lmp)
|
|
struct link_object *lop; /* link object searched for */
|
|
struct link_map *lmp; /* link map in which it occurs */
|
|
{
|
|
int i, j, k; /* working temporaries */
|
|
char *r; /* rule strings from main link map */
|
|
char *cp; /* working pointer */
|
|
char *so = NULL; /* return string */
|
|
struct dd *ddp; /* directory loop temporary */
|
|
static int once = 1; /* once-only flag */
|
|
|
|
/*
|
|
* Once-only processing. Deal with security issues here. Enable
|
|
* the main program to specify additional "trusted" directories
|
|
* through it's link map rules.
|
|
*
|
|
* Historical note: until late in the development of 4.1, the
|
|
* LD_LIBRARY_PATH environment variable was simply ignored if
|
|
* we were running at setxid program. However, this check was
|
|
* redundant, as the elements from the path are checked for trust
|
|
* anyway, and it inadvertantly prevented a program from allowing
|
|
c * the path variable to influence the search among trusted directories.
|
|
* Now, the library path variable is allowed, but its elements are
|
|
* ignored if they do not specify trusted directories. This assumes
|
|
* that the trust relationship among the directories in the path is
|
|
* reflexive (i.e., the ordering contributes nothing to the trust.)
|
|
*
|
|
* Additional note: "ld" allows relative path names to be put into
|
|
* the link rules for an executable. This, in the case of setxid
|
|
* programs is simply too much rope for people -- as they blithely
|
|
* use relative pathnames in construction but then are surprised
|
|
* to find them used again at execution. This obscures the basic
|
|
* problem of having relative resources when building a secure
|
|
* executable, but it's clear that this is just too much rope for
|
|
* people. So, "also secure" directories are only those that are
|
|
* absolute.
|
|
*/
|
|
if (once) {
|
|
|
|
/*
|
|
* If we're secure, and the main link_map has been established,
|
|
* then see if it has any embedded rules. If so, copy them
|
|
* onto the heap, and make a pass through counting the number
|
|
* of directories that are in the list. Then allocate an
|
|
* array of char *'s for "also_secure" of the appropriate
|
|
* length, and step through the processed string to initialize
|
|
* the array. Zero the last element. N.B.: only absolute
|
|
* directories (those beginning with "/") are included in
|
|
* this list.
|
|
*/
|
|
if ((*is_secure)() && (mlmp != NULL)) {
|
|
r = &TEXTBASE(mlmp)[mlmp->lm_ld->ld_un.ld_2->ld_rules];
|
|
if (r != TEXTBASE(mlmp)) {
|
|
cp = (*heap_malloc)(strlen(r) + 1);
|
|
(void) strcpy(cp, r);
|
|
for (r = cp, i = 0; get_next_name(&r); i++)
|
|
;
|
|
also_secure = (char **)(*heap_malloc)((i + 1) *
|
|
sizeof (char *));
|
|
for (j = k = 0; j < i; j++) {
|
|
if (*cp == '/')
|
|
also_secure[k++] = cp;
|
|
while (*cp++)
|
|
;
|
|
}
|
|
also_secure[k] = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Security checks finished, set up the path contributed
|
|
* by the environment. As noted above, we no longer prune
|
|
* this if we're running "secure" -- the lower levels take
|
|
* care of it for us.
|
|
*/
|
|
if (library_path && *library_path) {
|
|
es = (*heap_malloc)(strlen(library_path) + 1);
|
|
(void) strcpy(es, library_path);
|
|
} else
|
|
es = (char *)NULL;
|
|
once = 0;
|
|
}
|
|
for (ddp = get_next_dir((struct dd *)NULL, lmp);
|
|
ddp; ddp = get_next_dir(ddp, lmp))
|
|
if (so = ask_db(ddp->dd_db, TEXTBASE(lmp), lop))
|
|
break;
|
|
return (so);
|
|
}
|
|
|
|
/*
|
|
* Delete the databases that came from the mmapped file and foreach
|
|
* dd element reset the database pointer
|
|
*/
|
|
void
|
|
lo_flush()
|
|
{
|
|
struct dd *ddp; /* working dd pointer */
|
|
|
|
dbd_flush();
|
|
for (ddp = ddhead; ddp; ddp = ddp->dd_next)
|
|
ddp->dd_db = lo_cache((char *)&AP(ddp->dd_db)[ddp->dd_db->db_name]);
|
|
}
|
|
|
|
/*
|
|
* Given a db - find the highest shared versioned object. The
|
|
* highest versioned object is the .so with a matching major number
|
|
* but the highest minor number
|
|
*/
|
|
static char *
|
|
ask_db(dbp, base, lop)
|
|
struct db *dbp;
|
|
caddr_t base;
|
|
struct link_object *lop;
|
|
{
|
|
char *name = (char *)&(base[(int)lop->lo_name]);
|
|
struct dbe *ep;
|
|
struct link_object *tlop;
|
|
int index;
|
|
char l[20];
|
|
|
|
/*
|
|
* Search appropriate hash bucket for a matching entry.
|
|
*/
|
|
index = hash(name, strlen(name), lop->lo_major);
|
|
for (ep = (struct dbe *)&(dbp->db_hash[index]); (ep && ep->dbe_lop);
|
|
ep = ep->dbe_next == 0 ? NULL :
|
|
(struct dbe *)&AP(dbp)[ep->dbe_next]) {
|
|
tlop = (struct link_object *)&AP(dbp)[ep->dbe_lop];
|
|
if (tlop->lo_major == lop->lo_major)
|
|
if (!strcmp((char *)&AP(dbp)[tlop->lo_name], name))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If no entry was found, we've lost.
|
|
*/
|
|
if (!(ep && ep->dbe_lop))
|
|
return (NULL);
|
|
sprintf(l, "%d", lop->lo_minor);
|
|
if (verscmp(l, &AP(dbp)[ep->dbe_name] + tlop->lo_minor) > 0)
|
|
fprintf(stderr,
|
|
"ld.so: warning: %s has older revision than expected %d\n",
|
|
&AP(dbp)[ep->dbe_name], lop->lo_minor);
|
|
return (&AP(dbp)[ep->dbe_name]);
|
|
}
|
|
|
|
/*
|
|
* Given a directory descriptor, find the next one in a list. List
|
|
* is lazily evaluated, and is sensitive to changes in link maps.
|
|
*/
|
|
static struct dd *
|
|
get_next_dir(ddp, lmp)
|
|
struct dd *ddp; /* previous directory (or NULL) */
|
|
struct link_map *lmp; /* link map in which it occurs */
|
|
{
|
|
struct dd *rddp; /* return value */
|
|
struct dd **ddpp; /* insertion point temporary */
|
|
struct db *dbp; /* points to db */
|
|
char *ds; /* directory name string */
|
|
static char *ldr; /* link_dynamic ld_rules cache */
|
|
static int dhused = 0; /* default list inserted */
|
|
static int rsused = 0; /* lmp rules have been copied */
|
|
static char *rs; /* lmp rules string */
|
|
static char **dcpp = default_dirs;
|
|
/* default directory list position */
|
|
|
|
static struct dd *etddp = NULL;
|
|
/* environment tail pointer */
|
|
static struct dd *dhddp = NULL; /* defaults head pointer */
|
|
static struct link_map *plmp = NULL;
|
|
/* previous link map pointer */
|
|
|
|
/*
|
|
* If link map has changed, must discard any old values for
|
|
* its entries and start over.
|
|
*/
|
|
if (plmp != lmp) {
|
|
if (etddp)
|
|
etddp->dd_next = (struct dd *)NULL;
|
|
else
|
|
ddhead = 0;
|
|
plmp = lmp;
|
|
dhused = 0;
|
|
rs = NULL;
|
|
if ((ldr = &TEXTBASE(lmp)[lmp->lm_ld->ld_un.ld_2->ld_rules])
|
|
== TEXTBASE(lmp))
|
|
rsused = 1;
|
|
else
|
|
rsused = 0;
|
|
}
|
|
|
|
/*
|
|
* Get the next entry on the list if any. If we're starting
|
|
* at the front, set pointers accordingly.
|
|
*/
|
|
if (ddp) {
|
|
rddp = ddp->dd_next;
|
|
ddpp = &ddp->dd_next;
|
|
} else {
|
|
rddp = ddhead;
|
|
ddpp = &ddhead;
|
|
}
|
|
|
|
/*
|
|
* If we have a block, we're done -- return it.
|
|
*/
|
|
if (rddp)
|
|
return (rddp);
|
|
|
|
/*
|
|
* Ran off the end of the list, have to get another block if
|
|
* possible. Try first with the environment.
|
|
*/
|
|
while (es)
|
|
if (ds = get_next_name(&es))
|
|
if (secure_directory(ds))
|
|
if (dbp = lo_cache(ds))
|
|
return (etddp = new_dd(ddpp, dbp));
|
|
|
|
/*
|
|
* Environment is exhausted, search the link_map specific rules.
|
|
*/
|
|
if (!rsused) {
|
|
rs = (*heap_malloc)(strlen(ldr) + 1);
|
|
(void) strcpy(rs, ldr);
|
|
rsused = 1;
|
|
}
|
|
while (rs)
|
|
if (ds = get_next_name(&rs))
|
|
if (secure_directory(ds))
|
|
if (dbp = lo_cache(ds))
|
|
return (new_dd(ddpp, dbp));
|
|
|
|
/*
|
|
* Environment and link_map specific rules exhausted, now examine
|
|
* the default directories. If we've got a head and it has not
|
|
* yet been inserted, just use that.
|
|
*/
|
|
if (dhddp && !dhused) {
|
|
*ddpp = dhddp;
|
|
dhused = 1;
|
|
return (dhddp);
|
|
}
|
|
|
|
/*
|
|
* Only hope now is that we have more default directories to use.
|
|
*/
|
|
while (ds = *dcpp++)
|
|
if (secure_directory(ds))
|
|
if (dbp = lo_cache(ds)) {
|
|
rddp = new_dd(ddpp, dbp);
|
|
/*
|
|
* dhused = 1 since ddhead is also
|
|
* dhddp
|
|
*/
|
|
if (!dhddp) {
|
|
dhddp = rddp;
|
|
dhused = 1;
|
|
}
|
|
return (rddp);
|
|
}
|
|
|
|
/*
|
|
* Out of directories, they lose.
|
|
*/
|
|
return ((struct dd *)NULL);
|
|
}
|
|
|
|
/*
|
|
* Extract list of directories needed from colon separated string.
|
|
*/
|
|
static char *
|
|
get_next_name(list)
|
|
char **list;
|
|
{
|
|
char *lp = *list;
|
|
char *cp = *list;;
|
|
|
|
if (lp != NULL && *lp != '\0') {
|
|
while (*lp != '\0' && *lp != ':')
|
|
lp++;
|
|
if (*lp == ':') {
|
|
*lp = '\0';
|
|
*list = lp + 1;
|
|
if (**list == '\0')
|
|
*list = NULL;
|
|
} else
|
|
*list = NULL;
|
|
}
|
|
return(cp);
|
|
}
|
|
|
|
/*
|
|
* Allocate a new dd, append it after ddpp and set the dd_dbp to dbp.
|
|
*/
|
|
static struct dd *
|
|
new_dd(ddpp, dbp)
|
|
struct dd **ddpp; /* insertion point */
|
|
struct db *dbp; /* db associated with this dd */
|
|
{
|
|
struct dd *ddp; /* working dd ptr. */
|
|
|
|
ddp = (struct dd *)(*heap_malloc)(sizeof(struct dd));
|
|
ddp->dd_db = dbp;
|
|
ddp->dd_next = NULL;
|
|
*ddpp = ddp;
|
|
return (ddp);
|
|
}
|
|
|
|
/*
|
|
* Indicate whether "ds" is a directory we trust. If
|
|
* security function indicates we don't care, then just return true.
|
|
*/
|
|
static int
|
|
secure_directory(ds)
|
|
char *ds; /* directory string */
|
|
{
|
|
char **cpp; /* working (char **)string */
|
|
|
|
if ((*is_secure)()) {
|
|
for (cpp = secure_dirs; *cpp; cpp++)
|
|
if (strcmp(ds, *cpp) == 0)
|
|
return (1);
|
|
if (also_secure)
|
|
for (cpp = also_secure; *cpp; cpp++)
|
|
if (strcmp(ds, *cpp) == 0)
|
|
return (1);
|
|
return (0);
|
|
} else
|
|
return (1);
|
|
}
|
|
|