From patchwork Wed Sep 26 11:11:30 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 29546 Received: (qmail 39613 invoked by alias); 26 Sep 2018 11:20:49 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 39603 invoked by uid 89); 26 Sep 2018 11:20:48 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-25.4 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, RCVD_IN_DNSWL_NONE, SPF_HELO_PASS autolearn=ham version=3.3.2 spammy=UD:bfd.h, bfd.h, bfdh, preserving X-HELO: gateway24.websitewelcome.com Received: from gateway24.websitewelcome.com (HELO gateway24.websitewelcome.com) (192.185.51.209) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Wed, 26 Sep 2018 11:20:45 +0000 Received: from cm11.websitewelcome.com (cm11.websitewelcome.com [100.42.49.5]) by gateway24.websitewelcome.com (Postfix) with ESMTP id EDCFE16A27 for ; Wed, 26 Sep 2018 06:11:40 -0500 (CDT) Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with SMTP id 57jIgael3RPoj57jIgvVQT; Wed, 26 Sep 2018 06:11:40 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From: Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=NnKGlB5BvZ77iNVGTYCyTqrCndCA8+Q0RpkhhvmTCVY=; b=HNc0paPQr4+IJfWcsHe8TD+8J7 rPwaJivykbrD/7yZd0cIiFM1eGxz0372UIhSAsFIT/LYMkf1MzAA6QlsOfzff3rgVcVuPgi46zzyC TNMlIdZciIpBWqy64aNqKj9aL; Received: from 97-122-190-66.hlrn.qwest.net ([97.122.190.66]:44312 helo=bapiya.Home) by box5379.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.91) (envelope-from ) id 1g57jI-0014pw-L6; Wed, 26 Sep 2018 06:11:40 -0500 From: Tom Tromey To: gdb-patches@sourceware.org Cc: Tom Tromey Subject: [RFC 6/6] Cache a copy of the user's shell on macOS Date: Wed, 26 Sep 2018 05:11:30 -0600 Message-Id: <20180926111130.18956-7-tom@tromey.com> In-Reply-To: <20180926111130.18956-1-tom@tromey.com> References: <20180926111130.18956-1-tom@tromey.com> Recent versions of macOS have a feature called System Integrity Protection. Among other things, This feature prevents ptrace from tracing certain programs --- for example, the programs in /bin, which includes typical shells. This means that startup-with-shell does not work properly. This is PR cli/23364. Currently there is a workaround in gdb to disable startup-with-shell when this feature might be in use. This patch changes gdb to be a bit more precise about when startup-with-shell will not work, by checking whether the shell executable is restricted. If the shell is restricted, then this patch will also cause gdb to cache a copy of the shell in the gdb cache directory, and then reset the SHELL environment variable to point to this copy. This lets startup-with-shell work again. Tested on High Sierra by trying to start a program using redirection, and by running startup-with-shell.exp. gdb/ChangeLog 2018-09-26 Tom Tromey PR cli/23364: * darwin-nat.c (copied_shell): New global. (may_have_sip): Rename from should_disable_startup_with_shell. (copy_shell_to_cache, maybe_cache_shell): New functions. (darwin_nat_target::create_inferior): Update. Use copied_shell. --- gdb/ChangeLog | 9 +++ gdb/darwin-nat.c | 152 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 7 deletions(-) diff --git a/gdb/darwin-nat.c b/gdb/darwin-nat.c index eee9380d65..0e62ff4eae 100644 --- a/gdb/darwin-nat.c +++ b/gdb/darwin-nat.c @@ -38,6 +38,7 @@ #include "bfd.h" #include "bfd/mach-o.h" +#include #include #include #include @@ -61,7 +62,11 @@ #include #include "darwin-nat.h" +#include "filenames.h" #include "common/filestuff.h" +#include "common/gdb_unlinker.h" +#include "common/pathstuff.h" +#include "common/scoped_fd.h" #include "nat/fork-inferior.h" /* Quick overview. @@ -119,6 +124,10 @@ static int enable_mach_exceptions; /* Inferior that should report a fake stop event. */ static struct inferior *darwin_inf_fake_stop; +/* If non-NULL, the shell we actually invoke. See maybe_cache_shell + for details. */ +static const char *copied_shell; + #define PAGE_TRUNC(x) ((x) & ~(mach_page_size - 1)) #define PAGE_ROUND(x) PAGE_TRUNC((x) + mach_page_size - 1) @@ -1854,10 +1863,11 @@ darwin_execvp (const char *file, char * const argv[], char * const env[]) posix_spawnp (NULL, argv[0], NULL, &attr, argv, env); } -/* Read kernel version, and return TRUE on Sierra or later. */ +/* Read kernel version, and return TRUE if this host may have System + Integrity Protection (Sierra or later). */ static bool -should_disable_startup_with_shell () +may_have_sip () { char str[16]; size_t sz = sizeof (str); @@ -1873,6 +1883,132 @@ should_disable_startup_with_shell () return false; } +/* A helper for maybe_cache_shell. This copies the shell to the + cache. It will throw an exception on any failure. */ + +static void +copy_shell_to_cache (const char *shell, const std::string &new_name) +{ + scoped_fd from_fd (gdb_open_cloexec (shell, O_RDONLY, 0)); + if (from_fd.get () < 0) + error (_("Could not open shell (%s) for reading: %s"), + shell, safe_strerror (errno)); + + std::string new_dir = ldirname (new_name.c_str ()); + if (!mkdir_recursive (new_dir.c_str ())) + error (_("Could not make cache directory \"%s\": %s"), + new_dir.c_str (), safe_strerror (errno)); + + gdb::char_vector temp_name = make_temp_filename (new_name); + scoped_fd to_fd (gdb_mkstemp_cloexec (&temp_name[0])); + gdb::unlinker unlink_file_on_error (temp_name.data ()); + + if (to_fd.get () < 0) + error (_("Could not open temporary file \"%s\" for writing: %s"), + temp_name.data (), safe_strerror (errno)); + + if (fcopyfile (from_fd.get (), to_fd.get (), nullptr, + COPYFILE_STAT | COPYFILE_DATA) != 0) + error (_("Could not copy shell to cache as \"%s\": %s"), + temp_name.data (), safe_strerror (errno)); + + /* Be sure that the caching is atomic so that we don't get bad + results from multiple copies of gdb running at the same time. */ + if (rename (temp_name.data (), new_name.c_str ()) != 0) + error (_("Could not rename shell cache file to \"%s\": %s"), + new_name.c_str (), safe_strerror (errno)); + + unlink_file_on_error.keep (); +} + +/* If $SHELL is restricted, try to cache a copy. Starting with El + Capitan, macOS introduced System Integrity Protection. Among other + things, this prevents certain executables from being ptrace'd. In + particular, executables in /bin, like most shells, are affected. + To work around this, while preserving command-line glob expansion + and redirections, gdb will cache a copy of the shell. Return true + if all is well -- either the shell is not subject to SIP or it has + been successfully cached. Returns false if something failed. */ + +static bool +maybe_cache_shell () +{ + /* SF_RESTRICTED is defined in sys/stat.h and lets us determine if a + given file is subject to SIP. */ +#ifdef SF_RESTRICTED + + /* If a check fails we want to revert -- maybe the user deleted the + cache while gdb was running, or something like that. */ + copied_shell = nullptr; + + const char *shell = get_shell (); + if (!IS_ABSOLUTE_PATH (shell)) + { + warning (_("This version of macOS has System Integrity Protection.\n\ +Normally gdb would try to work around this by caching a copy of your shell,\n\ +but because your shell (%s) is not an absolute path, this is being skipped."), + shell); + return false; + } + + struct stat sb; + if (stat (shell, &sb) < 0) + { + warning (_("This version of macOS has System Integrity Protection.\n\ +Normally gdb would try to work around this by caching a copy of your shell,\n\ +but because gdb could not stat your shell (%s), this is being skipped.\n\ +The error was: %s"), + shell, safe_strerror (errno)); + return false; + } + + if ((sb.st_flags & SF_RESTRICTED) == 0) + return true; + + /* Put the copy somewhere like ~/Library/Caches/gdb/bin/sh. */ + std::string new_name = get_standard_cache_dir (); + if (!IS_DIR_SEPARATOR (new_name.back ()) && !IS_ABSOLUTE_PATH (shell)) + new_name.push_back ('/'); + new_name.append (shell); + + /* Maybe it was cached by some earlier gdb. */ + if (stat (new_name.c_str (), &sb) != 0 || !S_ISREG (sb.st_mode)) + { + TRY + { + copy_shell_to_cache (shell, new_name); + } + CATCH (ex, RETURN_MASK_ERROR) + { + warning (_("This version of macOS has System Integrity Protection.\n\ +Because `startup-with-shell' is enabled, gdb tried to work around SIP by\n\ +caching a copy of your shell. However, this failed:\n\ +%s\n\ +If you correct the problem, gdb will automatically try again the next time\n\ +you \"run\". To prevent these attempts, you can use:\n\ + set startup-with-shell off"), + ex.message); + return false; + } + END_CATCH + + printf_filtered (_("Note: this version of macOS has System Integrity Protection.\n\ +Because `startup-with-shell' is enabled, gdb has worked around this by\n\ +caching a copy of your shell. The shell used by \"run\" is now:\n\ + %s\n"), + new_name.c_str ()); + } + + /* We need to make sure that the new name has the correct lifetime + for setenv. */ + static std::string saved_shell = std::move (new_name); + copied_shell = saved_shell.c_str (); + +#endif /* SF_RESTRICTED */ + + return true; +} + void darwin_nat_target::create_inferior (const char *exec_file, const std::string &allargs, @@ -1880,16 +2016,18 @@ darwin_nat_target::create_inferior (const char *exec_file, { gdb::optional> restore_startup_with_shell; - if (startup_with_shell && should_disable_startup_with_shell ()) + if (startup_with_shell && may_have_sip ()) { - warning (_("startup-with-shell not supported on this macOS version," - " disabling it.")); - restore_startup_with_shell.emplace (&startup_with_shell, 0); + if (!maybe_cache_shell ()) + { + warning (_("startup-with-shell is now temporarily disabled")); + restore_startup_with_shell.emplace (&startup_with_shell, 0); + } } /* Do the hard work. */ fork_inferior (exec_file, allargs, env, darwin_ptrace_me, - darwin_ptrace_him, darwin_pre_ptrace, NULL, + darwin_ptrace_him, darwin_pre_ptrace, copied_shell, darwin_execvp); }