aboutsummaryrefslogtreecommitdiff
path: root/elf/dl-load.c
diff options
context:
space:
mode:
Diffstat (limited to 'elf/dl-load.c')
-rw-r--r--elf/dl-load.c213
1 files changed, 134 insertions, 79 deletions
diff --git a/elf/dl-load.c b/elf/dl-load.c
index c52d011..e81601f 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -121,12 +121,6 @@ is_trusted_path_normalize (const char *path, size_t len)
if (len == 0)
return false;
- if (*path == ':')
- {
- ++path;
- --len;
- }
-
char *npath = (char *) alloca (len + 2);
char *wnp = npath;
while (*path != '\0')
@@ -177,114 +171,165 @@ is_trusted_path_normalize (const char *path, size_t len)
return false;
}
+/* Given a substring starting at INPUT, just after the DST '$' start
+ token, determine if INPUT contains DST token REF, following the
+ ELF gABI rules for DSTs:
+
+ * Longest possible sequence using the rules (greedy).
+
+ * Must start with a $ (enforced by caller).
+
+ * Must follow $ with one underscore or ASCII [A-Za-z] (caller
+ follows these rules for REF) or '{' (start curly quoted name).
+
+ * Must follow first two characters with zero or more [A-Za-z0-9_]
+ (enforced by caller) or '}' (end curly quoted name).
+ If the sequence is a DST matching REF then the length of the DST
+ (excluding the $ sign but including curly braces, if any) is
+ returned, otherwise 0. */
static size_t
-is_dst (const char *start, const char *name, const char *str, int secure)
+is_dst (const char *input, const char *ref)
{
- size_t len;
bool is_curly = false;
- if (name[0] == '{')
+ /* Is a ${...} input sequence? */
+ if (input[0] == '{')
{
is_curly = true;
- ++name;
+ ++input;
}
- len = 0;
- while (name[len] == str[len] && name[len] != '\0')
- ++len;
-
- if (is_curly)
- {
- if (name[len] != '}')
- return 0;
-
- /* Point again at the beginning of the name. */
- --name;
- /* Skip over closing curly brace and adjust for the --name. */
- len += 2;
- }
- else if (name[len] != '\0' && name[len] != '/')
- return 0;
-
- if (__glibc_unlikely (secure)
- && ((name[len] != '\0' && name[len] != '/')
- || (name != start + 1)))
+ /* Check for matching name, following closing curly brace (if
+ required), or trailing characters which are part of an
+ identifier. */
+ size_t rlen = strlen (ref);
+ if (strncmp (input, ref, rlen) != 0
+ || (is_curly && input[rlen] != '}')
+ || ((input[rlen] >= 'A' && input[rlen] <= 'Z')
+ || (input[rlen] >= 'a' && input[rlen] <= 'z')
+ || (input[rlen] >= '0' && input[rlen] <= '9')
+ || (input[rlen] == '_')))
return 0;
- return len;
+ if (is_curly)
+ /* Count the two curly braces. */
+ return rlen + 2;
+ else
+ return rlen;
}
-
+/* INPUT is the start of a DST sequence at the first '$' occurrence.
+ If there is a DST we call into _dl_dst_count to count the number of
+ DSTs. We count all known DSTs regardless of __libc_enable_secure;
+ the caller is responsible for enforcing the security of the
+ substitution rules (usually _dl_dst_substitute). */
size_t
-_dl_dst_count (const char *name)
+_dl_dst_count (const char *input)
{
- const char *const start = name;
size_t cnt = 0;
+ input = strchr (input, '$');
+
+ /* Most likely there is no DST. */
+ if (__glibc_likely (input == NULL))
+ return 0;
+
do
{
size_t len;
- /* $ORIGIN is not expanded for SUID/GUID programs (except if it
- is $ORIGIN alone) and it must always appear first in path. */
- ++name;
- if ((len = is_dst (start, name, "ORIGIN", __libc_enable_secure)) != 0
- || (len = is_dst (start, name, "PLATFORM", 0)) != 0
- || (len = is_dst (start, name, "LIB", 0)) != 0)
+ ++input;
+ /* All DSTs must follow ELF gABI rules, see is_dst (). */
+ if ((len = is_dst (input, "ORIGIN")) != 0
+ || (len = is_dst (input, "PLATFORM")) != 0
+ || (len = is_dst (input, "LIB")) != 0)
++cnt;
- name = strchr (name + len, '$');
+ /* There may be more than one DST in the input. */
+ input = strchr (input + len, '$');
}
- while (name != NULL);
+ while (input != NULL);
return cnt;
}
-
+/* Process INPUT for DSTs and store in RESULT using the information
+ from link map L to resolve the DSTs. This function only handles one
+ path at a time and does not handle colon-separated path lists (see
+ fillin_rpath ()). Lastly the size of result in bytes should be at
+ least equal to the value returned by DL_DST_REQUIRED. Note that it
+ is possible for a DT_NEEDED, DT_AUXILIARY, and DT_FILTER entries to
+ have colons, but we treat those as literal colons here, not as path
+ list delimeters. */
char *
-_dl_dst_substitute (struct link_map *l, const char *name, char *result)
+_dl_dst_substitute (struct link_map *l, const char *input, char *result)
{
- const char *const start = name;
-
- /* Now fill the result path. While copying over the string we keep
- track of the start of the last path element. When we come across
- a DST we copy over the value or (if the value is not available)
- leave the entire path element out. */
+ /* Copy character-by-character from input into the working pointer
+ looking for any DSTs. We track the start of input and if we are
+ going to check for trusted paths, all of which are part of $ORIGIN
+ handling in SUID/SGID cases (see below). In some cases, like when
+ a DST cannot be replaced, we may set result to an empty string and
+ return. */
char *wp = result;
- char *last_elem = result;
+ const char *start = input;
bool check_for_trusted = false;
do
{
- if (__glibc_unlikely (*name == '$'))
+ if (__glibc_unlikely (*input == '$'))
{
const char *repl = NULL;
size_t len;
- ++name;
- if ((len = is_dst (start, name, "ORIGIN", __libc_enable_secure)) != 0)
+ ++input;
+ if ((len = is_dst (input, "ORIGIN")) != 0)
{
- repl = l->l_origin;
+ /* For SUID/GUID programs we normally ignore the path with
+ $ORIGIN in DT_RUNPATH, or DT_RPATH. However, there is
+ one exception to this rule, and it is:
+
+ * $ORIGIN appears as the first path element, and is
+ the only string in the path or is immediately
+ followed by a path separator and the rest of the
+ path.
+
+ * The path is rooted in a trusted directory.
+
+ This exception allows such programs to reference
+ shared libraries in subdirectories of trusted
+ directories. The use case is one of general
+ organization and deployment flexibility.
+ Trusted directories are usually such paths as "/lib64"
+ or "/usr/lib64", and the usual RPATHs take the form of
+ [$ORIGIN/../$LIB/somedir]. */
+ if (__glibc_unlikely (__libc_enable_secure)
+ && !(input == start + 1
+ && (input[len] == '\0' || input[len] == '/')))
+ repl = (const char *) -1;
+ else
+ repl = l->l_origin;
+
check_for_trusted = (__libc_enable_secure
&& l->l_type == lt_executable);
}
- else if ((len = is_dst (start, name, "PLATFORM", 0)) != 0)
+ else if ((len = is_dst (input, "PLATFORM")) != 0)
repl = GLRO(dl_platform);
- else if ((len = is_dst (start, name, "LIB", 0)) != 0)
+ else if ((len = is_dst (input, "LIB")) != 0)
repl = DL_DST_LIB;
if (repl != NULL && repl != (const char *) -1)
{
wp = __stpcpy (wp, repl);
- name += len;
+ input += len;
}
- else if (len > 1)
+ else if (len != 0)
{
- /* We cannot use this path element, the value of the
- replacement is unknown. */
- wp = last_elem;
- break;
+ /* We found a valid DST that we know about, but we could
+ not find a replacement value for it, therefore we
+ cannot use this path and discard it. */
+ *result = '\0';
+ return result;
}
else
/* No DST we recognize. */
@@ -292,16 +337,26 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result)
}
else
{
- *wp++ = *name++;
+ *wp++ = *input++;
}
}
- while (*name != '\0');
+ while (*input != '\0');
/* In SUID/SGID programs, after $ORIGIN expansion the normalized
- path must be rooted in one of the trusted directories. */
+ path must be rooted in one of the trusted directories. The $LIB
+ and $PLATFORM DST cannot in any way be manipulated by the caller
+ because they are fixed values that are set by the dynamic loader
+ and therefore any paths using just $LIB or $PLATFORM need not be
+ checked for trust, the authors of the binaries themselves are
+ trusted to have designed this correctly. Only $ORIGIN is tested in
+ this way because it may be manipulated in some ways with hard
+ links. */
if (__glibc_unlikely (check_for_trusted)
- && !is_trusted_path_normalize (last_elem, wp - last_elem))
- wp = last_elem;
+ && !is_trusted_path_normalize (result, wp - result))
+ {
+ *result = '\0';
+ return result;
+ }
*wp = '\0';
@@ -309,13 +364,13 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result)
}
-/* Return copy of argument with all recognized dynamic string tokens
- ($ORIGIN and $PLATFORM for now) replaced. On some platforms it
- might not be possible to determine the path from which the object
- belonging to the map is loaded. In this case the path element
- containing $ORIGIN is left out. */
+/* Return a malloc allocated copy of INPUT with all recognized DSTs
+ replaced. On some platforms it might not be possible to determine the
+ path from which the object belonging to the map is loaded. In this
+ case the path containing the DST is left out. On error NULL
+ is returned. */
static char *
-expand_dynamic_string_token (struct link_map *l, const char *s)
+expand_dynamic_string_token (struct link_map *l, const char *input)
{
/* We make two runs over the string. First we determine how large the
resulting string is and then we copy it over. Since this is no
@@ -325,22 +380,22 @@ expand_dynamic_string_token (struct link_map *l, const char *s)
size_t total;
char *result;
- /* Determine the number of DST elements. */
- cnt = DL_DST_COUNT (s);
+ /* Determine the number of DSTs. */
+ cnt = _dl_dst_count (input);
/* If we do not have to replace anything simply copy the string. */
if (__glibc_likely (cnt == 0))
- return __strdup (s);
+ return __strdup (input);
/* Determine the length of the substituted string. */
- total = DL_DST_REQUIRED (l, s, strlen (s), cnt);
+ total = DL_DST_REQUIRED (l, input, strlen (input), cnt);
/* Allocate the necessary memory. */
result = (char *) malloc (total + 1);
if (result == NULL)
return NULL;
- return _dl_dst_substitute (l, s, result);
+ return _dl_dst_substitute (l, input, result);
}