|
|
25845f |
commit 5aad5f617892e75d91d4c8fb7594ff35b610c042
|
|
|
25845f |
Author: Carlos O'Donell <carlos@redhat.com>
|
|
|
25845f |
Date: Tue Jun 5 23:55:17 2018 -0400
|
|
|
25845f |
|
|
|
25845f |
Improve DST handling (Bug 23102, Bug 21942, Bug 18018, Bug 23259).
|
|
|
25845f |
|
|
|
25845f |
This commit improves DST handling significantly in the following
|
|
|
25845f |
ways: firstly is_dst () is overhauled to correctly process DST
|
|
|
25845f |
sequences that would be accepted given the ELF gABI. This means that
|
|
|
25845f |
we actually now accept slightly more sequences than before. Now we
|
|
|
25845f |
accept $ORIGIN$ORIGIN, but in the past we accepted only $ORIGIN\0 or
|
|
|
25845f |
$ORIGIN/..., but this kind of behaviour results in unexpected
|
|
|
25845f |
and uninterpreted DST sequences being used as literal search paths
|
|
|
25845f |
leading to security defects. Therefore the first step in correcting
|
|
|
25845f |
this defect is making is_dst () properly account for all DSTs
|
|
|
25845f |
and making the function context free in the sense that it counts
|
|
|
25845f |
DSTs without knowledge of path, or AT_SECURE. Next, _dl_dst_count ()
|
|
|
25845f |
is also simplified to count all DSTs regardless of context.
|
|
|
25845f |
Then in _dl_dst_substitute () we reintroduce context-dependent
|
|
|
25845f |
processing for such things as AT_SECURE handling. At the level of
|
|
|
25845f |
_dl_dst_substitute we can have access to things like the true start
|
|
|
25845f |
of the string sequence to validate $ORIGIN-based paths rooted in
|
|
|
25845f |
trusted directories. Lastly, we tighten up the accepted sequences
|
|
|
25845f |
in AT_SECURE, and avoid leaving known unexpanded DSTs, this is
|
|
|
25845f |
noted in the NEWS entry.
|
|
|
25845f |
|
|
|
25845f |
Verified with a sequence of 68 tests on x86_64 that cover
|
|
|
25845f |
non-AT_SECURE and AT_SECURE testing using a sysroot (requires root
|
|
|
25845f |
to run). The tests cover cases for bug 23102, bug 21942, bug 18018,
|
|
|
25845f |
and bug 23259. These tests are not yet appropriate for the glibc
|
|
|
25845f |
regression testsuite, but with the upcoming test-in-container testing
|
|
|
25845f |
framework it should be possible to include these tests upstream soon.
|
|
|
25845f |
|
|
|
25845f |
See the mailing list for the tests:
|
|
|
25845f |
https://www.sourceware.org/ml/libc-alpha/2018-06/msg00251.html
|
|
|
25845f |
|
|
|
25845f |
Index: glibc-2.17-c758a686/elf/dl-deps.c
|
|
|
25845f |
===================================================================
|
|
|
25845f |
--- glibc-2.17-c758a686.orig/elf/dl-deps.c
|
|
|
25845f |
+++ glibc-2.17-c758a686/elf/dl-deps.c
|
|
|
25845f |
@@ -101,7 +101,7 @@ struct list
|
|
|
25845f |
({ \
|
|
|
25845f |
const char *__str = (str); \
|
|
|
25845f |
const char *__result = __str; \
|
|
|
25845f |
- size_t __dst_cnt = DL_DST_COUNT (__str); \
|
|
|
25845f |
+ size_t __dst_cnt = _dl_dst_count (__str); \
|
|
|
25845f |
\
|
|
|
25845f |
if (__dst_cnt != 0) \
|
|
|
25845f |
{ \
|
|
|
25845f |
Index: glibc-2.17-c758a686/elf/dl-dst.h
|
|
|
25845f |
===================================================================
|
|
|
25845f |
--- glibc-2.17-c758a686.orig/elf/dl-dst.h
|
|
|
25845f |
+++ glibc-2.17-c758a686/elf/dl-dst.h
|
|
|
25845f |
@@ -18,19 +18,6 @@
|
|
|
25845f |
|
|
|
25845f |
#include "trusted-dirs.h"
|
|
|
25845f |
|
|
|
25845f |
-/* Determine the number of DST elements in the name. Only if IS_PATH is
|
|
|
25845f |
- nonzero paths are recognized (i.e., multiple, ':' separated filenames). */
|
|
|
25845f |
-#define DL_DST_COUNT(name) \
|
|
|
25845f |
- ({ \
|
|
|
25845f |
- size_t __cnt = 0; \
|
|
|
25845f |
- const char *__sf = strchr (name, '$'); \
|
|
|
25845f |
- \
|
|
|
25845f |
- if (__builtin_expect (__sf != NULL, 0)) \
|
|
|
25845f |
- __cnt = _dl_dst_count (__sf); \
|
|
|
25845f |
- \
|
|
|
25845f |
- __cnt; })
|
|
|
25845f |
-
|
|
|
25845f |
-
|
|
|
25845f |
#ifdef SHARED
|
|
|
25845f |
# define IS_RTLD(l) (l) == &GL(dl_rtld_map)
|
|
|
25845f |
#else
|
|
|
25845f |
Index: glibc-2.17-c758a686/elf/dl-load.c
|
|
|
25845f |
===================================================================
|
|
|
25845f |
--- glibc-2.17-c758a686.orig/elf/dl-load.c
|
|
|
25845f |
+++ glibc-2.17-c758a686/elf/dl-load.c
|
|
|
25845f |
@@ -174,12 +174,6 @@ is_trusted_path_normalize (const char *p
|
|
|
25845f |
if (len == 0)
|
|
|
25845f |
return false;
|
|
|
25845f |
|
|
|
25845f |
- if (*path == ':')
|
|
|
25845f |
- {
|
|
|
25845f |
- ++path;
|
|
|
25845f |
- --len;
|
|
|
25845f |
- }
|
|
|
25845f |
-
|
|
|
25845f |
char *npath = (char *) alloca (len + 2);
|
|
|
25845f |
char *wnp = npath;
|
|
|
25845f |
while (*path != '\0')
|
|
|
25845f |
@@ -230,120 +224,172 @@ is_trusted_path_normalize (const char *p
|
|
|
25845f |
return false;
|
|
|
25845f |
}
|
|
|
25845f |
|
|
|
25845f |
+/* Given a substring starting at INPUT, just after the DST '$' start
|
|
|
25845f |
+ token, determine if INPUT contains DST token REF, following the
|
|
|
25845f |
+ ELF gABI rules for DSTs:
|
|
|
25845f |
+
|
|
|
25845f |
+ * Longest possible sequence using the rules (greedy).
|
|
|
25845f |
+
|
|
|
25845f |
+ * Must start with a $ (enforced by caller).
|
|
|
25845f |
+
|
|
|
25845f |
+ * Must follow $ with one underscore or ASCII [A-Za-z] (caller
|
|
|
25845f |
+ follows these rules for REF) or '{' (start curly quoted name).
|
|
|
25845f |
+
|
|
|
25845f |
+ * Must follow first two characters with zero or more [A-Za-z0-9_]
|
|
|
25845f |
+ (enforced by caller) or '}' (end curly quoted name).
|
|
|
25845f |
|
|
|
25845f |
+ If the sequence is a DST matching REF then the length of the DST
|
|
|
25845f |
+ (excluding the $ sign but including curly braces, if any) is
|
|
|
25845f |
+ returned, otherwise 0. */
|
|
|
25845f |
static size_t
|
|
|
25845f |
-is_dst (const char *start, const char *name, const char *str, int secure)
|
|
|
25845f |
+is_dst (const char *input, const char *ref)
|
|
|
25845f |
{
|
|
|
25845f |
- size_t len;
|
|
|
25845f |
bool is_curly = false;
|
|
|
25845f |
|
|
|
25845f |
- if (name[0] == '{')
|
|
|
25845f |
+ /* Is a ${...} input sequence? */
|
|
|
25845f |
+ if (input[0] == '{')
|
|
|
25845f |
{
|
|
|
25845f |
is_curly = true;
|
|
|
25845f |
- ++name;
|
|
|
25845f |
+ ++input;
|
|
|
25845f |
}
|
|
|
25845f |
|
|
|
25845f |
- len = 0;
|
|
|
25845f |
- while (name[len] == str[len] && name[len] != '\0')
|
|
|
25845f |
- ++len;
|
|
|
25845f |
-
|
|
|
25845f |
- if (is_curly)
|
|
|
25845f |
- {
|
|
|
25845f |
- if (name[len] != '}')
|
|
|
25845f |
- return 0;
|
|
|
25845f |
-
|
|
|
25845f |
- /* Point again at the beginning of the name. */
|
|
|
25845f |
- --name;
|
|
|
25845f |
- /* Skip over closing curly brace and adjust for the --name. */
|
|
|
25845f |
- len += 2;
|
|
|
25845f |
- }
|
|
|
25845f |
- else if (name[len] != '\0' && name[len] != '/')
|
|
|
25845f |
- return 0;
|
|
|
25845f |
-
|
|
|
25845f |
- if (__builtin_expect (secure, 0)
|
|
|
25845f |
- && ((name[len] != '\0' && name[len] != '/')
|
|
|
25845f |
- || (name != start + 1)))
|
|
|
25845f |
+ /* Check for matching name, following closing curly brace (if
|
|
|
25845f |
+ required), or trailing characters which are part of an
|
|
|
25845f |
+ identifier. */
|
|
|
25845f |
+ size_t rlen = strlen (ref);
|
|
|
25845f |
+ if (strncmp (input, ref, rlen) != 0
|
|
|
25845f |
+ || (is_curly && input[rlen] != '}')
|
|
|
25845f |
+ || ((input[rlen] >= 'A' && input[rlen] <= 'Z')
|
|
|
25845f |
+ || (input[rlen] >= 'a' && input[rlen] <= 'z')
|
|
|
25845f |
+ || (input[rlen] >= '0' && input[rlen] <= '9')
|
|
|
25845f |
+ || (input[rlen] == '_')))
|
|
|
25845f |
return 0;
|
|
|
25845f |
|
|
|
25845f |
- return len;
|
|
|
25845f |
+ if (is_curly)
|
|
|
25845f |
+ /* Count the two curly braces. */
|
|
|
25845f |
+ return rlen + 2;
|
|
|
25845f |
+ else
|
|
|
25845f |
+ return rlen;
|
|
|
25845f |
}
|
|
|
25845f |
|
|
|
25845f |
-
|
|
|
25845f |
+/* INPUT is the start of a DST sequence at the first '$' occurrence.
|
|
|
25845f |
+ If there is a DST we call into _dl_dst_count to count the number of
|
|
|
25845f |
+ DSTs. We count all known DSTs regardless of __libc_enable_secure;
|
|
|
25845f |
+ the caller is responsible for enforcing the security of the
|
|
|
25845f |
+ substitution rules (usually _dl_dst_substitute). */
|
|
|
25845f |
size_t
|
|
|
25845f |
-_dl_dst_count (const char *name)
|
|
|
25845f |
+_dl_dst_count (const char *input)
|
|
|
25845f |
{
|
|
|
25845f |
- const char *const start = name;
|
|
|
25845f |
size_t cnt = 0;
|
|
|
25845f |
|
|
|
25845f |
+ input = strchr (input, '$');
|
|
|
25845f |
+
|
|
|
25845f |
+ /* Most likely there is no DST. */
|
|
|
25845f |
+ if (__glibc_likely (input == NULL))
|
|
|
25845f |
+ return 0;
|
|
|
25845f |
+
|
|
|
25845f |
do
|
|
|
25845f |
{
|
|
|
25845f |
size_t len;
|
|
|
25845f |
|
|
|
25845f |
- /* $ORIGIN is not expanded for SUID/GUID programs (except if it
|
|
|
25845f |
- is $ORIGIN alone) and it must always appear first in path. */
|
|
|
25845f |
- ++name;
|
|
|
25845f |
- if ((len = is_dst (start, name, "ORIGIN", INTUSE(__libc_enable_secure))) != 0
|
|
|
25845f |
- || (len = is_dst (start, name, "PLATFORM", 0)) != 0
|
|
|
25845f |
- || (len = is_dst (start, name, "LIB", 0)) != 0)
|
|
|
25845f |
+ ++input;
|
|
|
25845f |
+ /* All DSTs must follow ELF gABI rules, see is_dst (). */
|
|
|
25845f |
+ if ((len = is_dst (input, "ORIGIN")) != 0
|
|
|
25845f |
+ || (len = is_dst (input, "PLATFORM")) != 0
|
|
|
25845f |
+ || (len = is_dst (input, "LIB")) != 0)
|
|
|
25845f |
++cnt;
|
|
|
25845f |
|
|
|
25845f |
- name = strchr (name + len, '$');
|
|
|
25845f |
+ /* There may be more than one DST in the input. */
|
|
|
25845f |
+ input = strchr (input + len, '$');
|
|
|
25845f |
}
|
|
|
25845f |
- while (name != NULL);
|
|
|
25845f |
+ while (input != NULL);
|
|
|
25845f |
|
|
|
25845f |
return cnt;
|
|
|
25845f |
}
|
|
|
25845f |
|
|
|
25845f |
-
|
|
|
25845f |
+/* Process INPUT for DSTs and store in RESULT using the information
|
|
|
25845f |
+ from link map L to resolve the DSTs. This function only handles one
|
|
|
25845f |
+ path at a time and does not handle colon-separated path lists (see
|
|
|
25845f |
+ fillin_rpath ()). Lastly the size of result in bytes should be at
|
|
|
25845f |
+ least equal to the value returned by DL_DST_REQUIRED. Note that it
|
|
|
25845f |
+ is possible for a DT_NEEDED, DT_AUXILIARY, and DT_FILTER entries to
|
|
|
25845f |
+ have colons, but we treat those as literal colons here, not as path
|
|
|
25845f |
+ list delimeters. */
|
|
|
25845f |
char *
|
|
|
25845f |
-_dl_dst_substitute (struct link_map *l, const char *name, char *result)
|
|
|
25845f |
+_dl_dst_substitute (struct link_map *l, const char *input, char *result)
|
|
|
25845f |
{
|
|
|
25845f |
- const char *const start = name;
|
|
|
25845f |
-
|
|
|
25845f |
- /* Now fill the result path. While copying over the string we keep
|
|
|
25845f |
- track of the start of the last path element. When we come accross
|
|
|
25845f |
- a DST we copy over the value or (if the value is not available)
|
|
|
25845f |
- leave the entire path element out. */
|
|
|
25845f |
+ /* Copy character-by-character from input into the working pointer
|
|
|
25845f |
+ looking for any DSTs. We track the start of input and if we are
|
|
|
25845f |
+ going to check for trusted paths, all of which are part of $ORIGIN
|
|
|
25845f |
+ handling in SUID/SGID cases (see below). In some cases, like when
|
|
|
25845f |
+ a DST cannot be replaced, we may set result to an empty string and
|
|
|
25845f |
+ return. */
|
|
|
25845f |
char *wp = result;
|
|
|
25845f |
- char *last_elem = result;
|
|
|
25845f |
+ const char *start = input;
|
|
|
25845f |
bool check_for_trusted = false;
|
|
|
25845f |
|
|
|
25845f |
do
|
|
|
25845f |
{
|
|
|
25845f |
- if (__builtin_expect (*name == '$', 0))
|
|
|
25845f |
+ if (__glibc_unlikely (*input == '$'))
|
|
|
25845f |
{
|
|
|
25845f |
const char *repl = NULL;
|
|
|
25845f |
size_t len;
|
|
|
25845f |
|
|
|
25845f |
- ++name;
|
|
|
25845f |
- if ((len = is_dst (start, name, "ORIGIN", INTUSE(__libc_enable_secure))) != 0)
|
|
|
25845f |
+ ++input;
|
|
|
25845f |
+ if ((len = is_dst (input, "ORIGIN")) != 0)
|
|
|
25845f |
{
|
|
|
25845f |
-#ifndef SHARED
|
|
|
25845f |
- if (l == NULL)
|
|
|
25845f |
- repl = _dl_get_origin ();
|
|
|
25845f |
+ /* For SUID/GUID programs we normally ignore the path with
|
|
|
25845f |
+ $ORIGIN in DT_RUNPATH, or DT_RPATH. However, there is
|
|
|
25845f |
+ one exception to this rule, and it is:
|
|
|
25845f |
+
|
|
|
25845f |
+ * $ORIGIN appears as the first path element, and is
|
|
|
25845f |
+ the only string in the path or is immediately
|
|
|
25845f |
+ followed by a path separator and the rest of the
|
|
|
25845f |
+ path.
|
|
|
25845f |
+
|
|
|
25845f |
+ * The path is rooted in a trusted directory.
|
|
|
25845f |
+
|
|
|
25845f |
+ This exception allows such programs to reference
|
|
|
25845f |
+ shared libraries in subdirectories of trusted
|
|
|
25845f |
+ directories. The use case is one of general
|
|
|
25845f |
+ organization and deployment flexibility.
|
|
|
25845f |
+ Trusted directories are usually such paths as "/lib64"
|
|
|
25845f |
+ or "/usr/lib64", and the usual RPATHs take the form of
|
|
|
25845f |
+ [$ORIGIN/../$LIB/somedir]. */
|
|
|
25845f |
+ if (__glibc_unlikely (__libc_enable_secure)
|
|
|
25845f |
+ && !(input == start + 1
|
|
|
25845f |
+ && (input[len] == '\0' || input[len] == '/')))
|
|
|
25845f |
+ repl = (const char *) -1;
|
|
|
25845f |
else
|
|
|
25845f |
+ {
|
|
|
25845f |
+#ifndef SHARED
|
|
|
25845f |
+ if (l == NULL)
|
|
|
25845f |
+ repl = _dl_get_origin ();
|
|
|
25845f |
+ else
|
|
|
25845f |
#endif
|
|
|
25845f |
- repl = l->l_origin;
|
|
|
25845f |
+ repl = l->l_origin;
|
|
|
25845f |
+ }
|
|
|
25845f |
|
|
|
25845f |
check_for_trusted = (INTUSE(__libc_enable_secure)
|
|
|
25845f |
&& l->l_type == lt_executable);
|
|
|
25845f |
}
|
|
|
25845f |
- else if ((len = is_dst (start, name, "PLATFORM", 0)) != 0)
|
|
|
25845f |
+ else if ((len = is_dst (input, "PLATFORM")) != 0)
|
|
|
25845f |
repl = GLRO(dl_platform);
|
|
|
25845f |
- else if ((len = is_dst (start, name, "LIB", 0)) != 0)
|
|
|
25845f |
+ else if ((len = is_dst (input, "LIB")) != 0)
|
|
|
25845f |
repl = DL_DST_LIB;
|
|
|
25845f |
|
|
|
25845f |
if (repl != NULL && repl != (const char *) -1)
|
|
|
25845f |
{
|
|
|
25845f |
wp = __stpcpy (wp, repl);
|
|
|
25845f |
- name += len;
|
|
|
25845f |
+ input += len;
|
|
|
25845f |
}
|
|
|
25845f |
- else if (len > 1)
|
|
|
25845f |
+ else if (len != 0)
|
|
|
25845f |
{
|
|
|
25845f |
- /* We cannot use this path element, the value of the
|
|
|
25845f |
- replacement is unknown. */
|
|
|
25845f |
- wp = last_elem;
|
|
|
25845f |
- break;
|
|
|
25845f |
+ /* We found a valid DST that we know about, but we could
|
|
|
25845f |
+ not find a replacement value for it, therefore we
|
|
|
25845f |
+ cannot use this path and discard it. */
|
|
|
25845f |
+ *result = '\0';
|
|
|
25845f |
+ return result;
|
|
|
25845f |
}
|
|
|
25845f |
else
|
|
|
25845f |
/* No DST we recognize. */
|
|
|
25845f |
@@ -351,16 +397,26 @@ _dl_dst_substitute (struct link_map *l,
|
|
|
25845f |
}
|
|
|
25845f |
else
|
|
|
25845f |
{
|
|
|
25845f |
- *wp++ = *name++;
|
|
|
25845f |
+ *wp++ = *input++;
|
|
|
25845f |
}
|
|
|
25845f |
}
|
|
|
25845f |
- while (*name != '\0');
|
|
|
25845f |
+ while (*input != '\0');
|
|
|
25845f |
|
|
|
25845f |
/* In SUID/SGID programs, after $ORIGIN expansion the normalized
|
|
|
25845f |
- path must be rooted in one of the trusted directories. */
|
|
|
25845f |
- if (__builtin_expect (check_for_trusted, false)
|
|
|
25845f |
- && !is_trusted_path_normalize (last_elem, wp - last_elem))
|
|
|
25845f |
- wp = last_elem;
|
|
|
25845f |
+ path must be rooted in one of the trusted directories. The $LIB
|
|
|
25845f |
+ and $PLATFORM DST cannot in any way be manipulated by the caller
|
|
|
25845f |
+ because they are fixed values that are set by the dynamic loader
|
|
|
25845f |
+ and therefore any paths using just $LIB or $PLATFORM need not be
|
|
|
25845f |
+ checked for trust, the authors of the binaries themselves are
|
|
|
25845f |
+ trusted to have designed this correctly. Only $ORIGIN is tested in
|
|
|
25845f |
+ this way because it may be manipulated in some ways with hard
|
|
|
25845f |
+ links. */
|
|
|
25845f |
+ if (__glibc_unlikely (check_for_trusted)
|
|
|
25845f |
+ && !is_trusted_path_normalize (result, wp - result))
|
|
|
25845f |
+ {
|
|
|
25845f |
+ *result = '\0';
|
|
|
25845f |
+ return result;
|
|
|
25845f |
+ }
|
|
|
25845f |
|
|
|
25845f |
*wp = '\0';
|
|
|
25845f |
|
|
|
25845f |
@@ -368,13 +424,13 @@ _dl_dst_substitute (struct link_map *l,
|
|
|
25845f |
}
|
|
|
25845f |
|
|
|
25845f |
|
|
|
25845f |
-/* Return copy of argument with all recognized dynamic string tokens
|
|
|
25845f |
- ($ORIGIN and $PLATFORM for now) replaced. On some platforms it
|
|
|
25845f |
- might not be possible to determine the path from which the object
|
|
|
25845f |
- belonging to the map is loaded. In this case the path element
|
|
|
25845f |
- containing $ORIGIN is left out. */
|
|
|
25845f |
+/* Return a malloc allocated copy of INPUT with all recognized DSTs
|
|
|
25845f |
+ replaced. On some platforms it might not be possible to determine the
|
|
|
25845f |
+ path from which the object belonging to the map is loaded. In this
|
|
|
25845f |
+ case the path containing the DST is left out. On error NULL
|
|
|
25845f |
+ is returned. */
|
|
|
25845f |
static char *
|
|
|
25845f |
-expand_dynamic_string_token (struct link_map *l, const char *s)
|
|
|
25845f |
+expand_dynamic_string_token (struct link_map *l, const char *input)
|
|
|
25845f |
{
|
|
|
25845f |
/* We make two runs over the string. First we determine how large the
|
|
|
25845f |
resulting string is and then we copy it over. Since this is no
|
|
|
25845f |
@@ -384,22 +440,22 @@ expand_dynamic_string_token (struct link
|
|
|
25845f |
size_t total;
|
|
|
25845f |
char *result;
|
|
|
25845f |
|
|
|
25845f |
- /* Determine the number of DST elements. */
|
|
|
25845f |
- cnt = DL_DST_COUNT (s);
|
|
|
25845f |
+ /* Determine the number of DSTs. */
|
|
|
25845f |
+ cnt = _dl_dst_count (input);
|
|
|
25845f |
|
|
|
25845f |
/* If we do not have to replace anything simply copy the string. */
|
|
|
25845f |
if (__builtin_expect (cnt, 0) == 0)
|
|
|
25845f |
- return local_strdup (s);
|
|
|
25845f |
+ return local_strdup (input);
|
|
|
25845f |
|
|
|
25845f |
/* Determine the length of the substituted string. */
|
|
|
25845f |
- total = DL_DST_REQUIRED (l, s, strlen (s), cnt);
|
|
|
25845f |
+ total = DL_DST_REQUIRED (l, input, strlen (input), cnt);
|
|
|
25845f |
|
|
|
25845f |
/* Allocate the necessary memory. */
|
|
|
25845f |
result = (char *) malloc (total + 1);
|
|
|
25845f |
if (result == NULL)
|
|
|
25845f |
return NULL;
|
|
|
25845f |
|
|
|
25845f |
- return _dl_dst_substitute (l, s, result);
|
|
|
25845f |
+ return _dl_dst_substitute (l, input, result);
|
|
|
25845f |
}
|
|
|
25845f |
|
|
|
25845f |
|