From patchwork Fri Mar 14 22:18:58 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Brooks Moses X-Patchwork-Id: 330515 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 0E9BC2C00C6 for ; Sat, 15 Mar 2014 09:19:21 +1100 (EST) DomainKey-Signature: a=rsa-sha1; c=nofws; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:from:to:cc:subject:date:message-id; q=dns; s= default; b=k8gRZIJ+Ad1pDsl/24+YBn0F+183EXWyGwyswSg66BF5iA6DARaLM DMPJVmqCYPL+dakLyOaY/tm7lRKefN/ZavazaIuUIuw0vsDDcEnz5Pyyoo2r8DI/ O7khvWQ9y0cw/JNYxKanI/1wRVnwNTVW+QyEANCqTOL9EOZ3d30PHs= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=sourceware.org; h=list-id :list-unsubscribe:list-subscribe:list-archive:list-post :list-help:sender:from:to:cc:subject:date:message-id; s=default; bh=WO5OS3dC2eJgCYOqhGcp9zCKnqI=; b=vSMUg1INO2rJ7s2VA0/zCa7vyYxB jTiBFf4lH8B3NDL6W2yukZ0Aoy9fTowA3oAKRbGF9tjIg7C+CRpZDvXFjTK7nPDF 5XBqzAzIgPBl5jhtzrtkMRxAIyKojGBja3YKvbg31GJ06VaiBDwP76oF/vyyTtU7 3T0XR2NJ1U3PgkY= Received: (qmail 14692 invoked by alias); 14 Mar 2014 22:19:16 -0000 Mailing-List: contact libc-alpha-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: libc-alpha-owner@sourceware.org Delivered-To: mailing list libc-alpha@sourceware.org Received: (qmail 14677 invoked by uid 89); 14 Mar 2014 22:19:15 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.7 required=5.0 tests=AWL, BAYES_00, RCVD_IN_DNSWL_LOW, SPF_PASS, T_RP_MATCHES_RCVD autolearn=ham version=3.3.2 X-HELO: mail-ie0-f202.google.com X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=8byRQVIR5WkNyYPhZSsASMPSJpIvHkB0jzt/MV4qYTw=; b=bLfLurMjdTgrlOvXcGb+i83unUQAkLpARSaPwC6dXSNhFEsrfe3MWEXO16Z6dXs08P T8OO4Uwz9OgKzuEwF6aAsIy/Oe2wsYEl55Edka5vcTb5qmGHws9wixeWb9L0QT3GClZb Pr8n7mYmX1oCDi2F2jd9SK/M6dPBAOf6KF1UNjBh9WvLM0rFxd0DONW0kU7iRUpZTgff nQwccJQW3BClOVatqPVtHYJZC5s5JL5ypu0Z7GstAE8EpQosdDJScrXGk8JWlQAr3QYJ 8QH/4Sl7FQrDMovKkTuoq8PMixfTwZHmq//N1D1IpkVJrbiUPuDCirqrB6fs4t9fl8M/ xe5w== X-Gm-Message-State: ALoCoQmvgIPMFP1Ezmn/10Kdxa+wr3nRJEGxMfsruMAcp4WsQw87yqJrfxlaERjWLGhClon7QVOAn643MsYyQ0Vd3V0bnc4gIu17orqEbP/YhEXC5uWJz/38H+ox5KCE1mXyTTl4dpLj8+GKV2ZuDlSyrnBbC6Jpo3KxqBojc32y5xcCgnxlkqHezKzSSWdMrIyD98DxL6HbxCSd0Ma2nhCKPhxvvi/fyw== X-Received: by 10.182.11.70 with SMTP id o6mr4148001obb.19.1394835547132; Fri, 14 Mar 2014 15:19:07 -0700 (PDT) From: Brooks Moses To: libc-alpha@sourceware.org Cc: "Carlos O'Donnell" , Brooks Moses Subject: Ping^5.1: [PATCH][v3] Add dynamic linker support for $EXEC_ORIGIN. Date: Fri, 14 Mar 2014 15:18:58 -0700 Message-Id: <1394835538-17931-1-git-send-email-bmoses@google.com> Resending to get this into the patch tracker. --- Updated to include Mike Frysinger's comments, as below. Mike suggests that this still needs signoff from someone more familiar with the ld.so code; any volunteers? On Sun, Dec 22, 2013 at 11:51 PM, Mike Frysinger wrote: > On Thursday 12 December 2013 17:13:39 Brooks Moses wrote: >> --- a/elf/dl-dst.h >> +++ b/elf/dl-dst.h >> >> + char *exec_origin = GLRO(dl_exec_origin_path); \ > > shouldn't that be const ? Yup, it should. Fixed. >> --- a/elf/rtld.c >> +++ b/elf/rtld.c >> >> +static void set_exec_origin_path(const char *exe_path); > > space before the ( Also fixed. > otherwise, looks OK, but probably should get sign off from someone more > familiar with the ldso code Thanks! - Brooks --- The $ORIGIN rpath token expands to the directory containing the true copy of the application executable -- which is to say, if the executable is invoked through a symlink, that symlink will be resolved in the $ORIGIN substitution. In some cases, such as symlink farms backed by cloud storage filesystems where the resolved path encodes effectively-arbitrary storage data, it is much more useful to have a rpath token that points to the non-expanded, as-called version of the executable path. This patch adds the $EXEC_ORIGIN token for this purpose, resolving to the executable path as passed to execve(). One potential security risk of such a token expansion is that, although copying a setuid/setgid executable (to change its $ORIGIN) loses the setuid/setgid bit, creating a symlink does not -- and so a user could inject arbitrary code into a setuid/setgid executable that references $EXEC_ORIGIN by creating an appropriate symlink. In order to slam the door shut on this security risk, this patch simply prohibits the use of $EXEC_ORIGIN in setuid/setgid binaries. --- 2013-12-08 Brooks Moses * elf/dl-support.c (_dl_exec_origin_path): New global variable. * elf/dl-dst.h (DL_DST_REQUIRED): Include _dl_exec_origin_path in computing dst_len. * elf/dl-load.c (_dl_dst_count): Also handle "EXEC_ORIGIN" token. (_dl_dst_substitute): Likewise. * elf/rtld.c (get_at_execfn): New static function. (get_directory): New static function. (set_exec_origin_path): New static function. (dl_main): Call set_exec_origin_path. * sysdeps/generic/ldsodefs.h (rtld_global_ro): Add _dl_exec_origin_path. elf/dl-dst.h | 9 ++++-- elf/dl-load.c | 10 ++++++- elf/dl-support.c | 3 ++ elf/rtld.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++ sysdeps/generic/ldsodefs.h | 3 ++ 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/elf/dl-dst.h b/elf/dl-dst.h index 3ed95d0..13cd0e7 100644 --- a/elf/dl-dst.h +++ b/elf/dl-dst.h @@ -65,8 +65,13 @@ else \ dst_len = (l)->l_origin == (char *) -1 \ ? 0 : strlen ((l)->l_origin); \ - dst_len = MAX (MAX (dst_len, GLRO(dl_platformlen)), \ - strlen (DL_DST_LIB)); \ + \ + const char *exec_origin = GLRO(dl_exec_origin_path); \ + size_t exec_origin_len = \ + (exec_origin == NULL) ? 0 : strlen (exec_origin); \ + \ + dst_len = MAX (MAX (MAX (dst_len, GLRO(dl_platformlen)), \ + strlen (DL_DST_LIB)), exec_origin_len); \ if (dst_len > 4) \ __len += __cnt * (dst_len - 4); \ } \ diff --git a/elf/dl-load.c b/elf/dl-load.c index d3e1cf8..04956dc 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -306,7 +306,8 @@ _dl_dst_count (const char *name, int is_path) if ((len = is_dst (start, name, "ORIGIN", is_path, INTUSE(__libc_enable_secure))) != 0 || (len = is_dst (start, name, "PLATFORM", is_path, 0)) != 0 - || (len = is_dst (start, name, "LIB", is_path, 0)) != 0) + || (len = is_dst (start, name, "LIB", is_path, 0)) != 0 + || (len = is_dst (start, name, "EXEC_ORIGIN", is_path, 0)) != 0) ++cnt; name = strchr (name + len, '$'); @@ -350,6 +351,13 @@ _dl_dst_substitute (struct link_map *l, const char *name, char *result, repl = GLRO(dl_platform); else if ((len = is_dst (start, name, "LIB", is_path, 0)) != 0) repl = DL_DST_LIB; + else if ((len = is_dst (start, name, "EXEC_ORIGIN", is_path, 0)) != 0) + { + if (INTUSE(__libc_enable_secure) != 0) + _dl_fatal_printf ("$EXEC_ORIGIN rpath entry not allowed in setuid/setgid executables.\n"); + + repl = GLRO(dl_exec_origin_path); + } if (repl != NULL && repl != (const char *) -1) { diff --git a/elf/dl-support.c b/elf/dl-support.c index 2023bd0..d2d427a 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -67,6 +67,9 @@ void *__libc_stack_end; /* Path where the binary is found. */ const char *_dl_origin_path; +/* Directory where the AT_EXECFN is found. */ +const char *_dl_exec_origin_path; + /* Nonzero if runtime lookup should not update the .got/.plt. */ int _dl_bind_not; diff --git a/elf/rtld.c b/elf/rtld.c index 3d207a3..0b3dd1f 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -51,6 +51,9 @@ extern __typeof (__mempcpy) __mempcpy attribute_hidden; extern __typeof (_exit) exit_internal asm ("_exit") attribute_hidden; #define _exit exit_internal +/* Given file path, return absolute directory path. */ +static char * get_directory (const char *file_path); + /* Helper function to handle errors while resolving symbols. */ static void print_unresolved (int errcode, const char *objname, const char *errsting); @@ -73,6 +76,9 @@ enum mode { normal, list, verify, trace }; all the entries. */ static void process_envvars (enum mode *modep); +/* Set GLRO(dl_exec_origin_path). */ +static void set_exec_origin_path (const char *exe_path); + #ifdef DL_ARGV_NOT_RELRO int _dl_argc attribute_hidden; char **_dl_argv = NULL; @@ -1032,6 +1038,8 @@ of this helper program; chances are you did not intend to run this program.\n\ in LIST\n\ --audit LIST use objects named in LIST as auditors\n"); + set_exec_origin_path (INTUSE(_dl_argv)[1]); + ++_dl_skip_args; --_dl_argc; ++INTUSE(_dl_argv); @@ -1126,6 +1134,8 @@ of this helper program; chances are you did not intend to run this program.\n\ } else { + set_exec_origin_path ((const char *) getauxval (AT_EXECFN)); + /* Create a link_map for the executable itself. This will be what dlopen on "" returns. */ main_map = _dl_new_object ((char *) "", "", lt_executable, NULL, @@ -2807,3 +2817,65 @@ print_statistics (hp_timing_t *rtld_total_timep) } #endif } + +/* Given file path, return an absolute directory path. + Examples: in: "/foo/bar/a.out", out: "/foo/bar/"; + in: "./a.out", out: "/dot/resolved/to/full/path/./". */ +static char * +get_directory (const char *file_path) +{ + assert (file_path != NULL); + + /* Find the end of the directory substring in file_path. */ + size_t path_len = strlen (file_path); + while (path_len > 0 && file_path[path_len - 1] != '/') + --path_len; + + /* Allocate space and set the path prefix according to whether or not + this is an absolute path. */ + char *dest; + char *full_dir_path; + if (file_path[0] == '/') + { + full_dir_path = malloc (path_len + 1); + assert (full_dir_path != NULL); + dest = full_dir_path; + } + else + { + /* For a relative path, we need to include space for the largest + possible current path, a joining '/', the relevant part of + file_path, and a trailing '\0'. */ + full_dir_path = malloc (PATH_MAX + path_len + 2); + assert (full_dir_path != NULL); + + char *status = __getcwd (full_dir_path, PATH_MAX); + assert (status != NULL); + + char *dest = __rawmemchr (full_dir_path, '\0'); + if (dest[-1] != '/') + *dest++ = '/'; + } + + if (path_len > 0) + dest = __mempcpy (dest, file_path, path_len); + *dest = '\0'; + + /* Confirm that the constructed path is valid. */ + struct stat64 st; + assert (__xstat64 (_STAT_VER, full_dir_path, &st) == 0); + + return full_dir_path; +} + +/* Set GLRO(dl_exec_origin_path). */ +static void +set_exec_origin_path (const char *exe_path) +{ + assert (GLRO(dl_exec_origin_path) == NULL); + + if (GLRO(dl_origin_path) != NULL) + GLRO(dl_exec_origin_path) = strdup (GLRO(dl_origin_path)); + else if (exe_path != NULL) + GLRO(dl_exec_origin_path) = get_directory (exe_path); +} diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 146aca4..561a763 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -518,6 +518,9 @@ struct rtld_global_ro /* Location of the binary. */ EXTERN const char *_dl_origin_path; + /* Directory where the AT_EXECFN is found. */ + EXTERN const char *_dl_exec_origin_path; + /* -1 if the dynamic linker should honor library load bias, 0 if not, -2 use the default (honor biases for normal binaries, don't honor for PIEs). */