[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH/RFC] do not source/exec scripts on noexec mount points
From: |
Mike Frysinger |
Subject: |
[PATCH/RFC] do not source/exec scripts on noexec mount points |
Date: |
Sat, 12 Dec 2015 16:01:26 -0500 |
From: Mike Frysinger <vapier@chromium.org>
Today, if you have a script that lives on a noexec mount point, the
kernel will reject attempts to run it directly:
$ printf '#!/bin/sh\necho hi\n' > /dev/shm/test.sh
$ chmod a+rx /dev/shm/test.sh
$ /dev/shm/test.sh
bash: /dev/shm/test.sh: Permission denied
But bash itself has no problem running this file:
$ bash /dev/shm/test.sh
hi
Or with letting other scripts run this file:
$ bash -c '. /dev/shm/test.sh'
hi
Or with reading the script from stdin:
$ bash </dev/shm/test.sh
hi
This detracts from the security of the overall system. People writing
scripts sometimes want to save/restore state (like variables) and will
restore the content from a noexec point using the aforementioned source
command without realizing that it executes code too. Of course their
code is wrong, but it would be nice if the system would catch & reject
it explicitly to stave of inadvertent usage.
This is not a perfect solution as it can still be worked around by
inlining the code itself:
$ bash -c "$(cat /dev/shm/test.sh)"
hi
But this makes things a bit harder for malicious attackers (depending
how exactly they've managed to escalate), but it also helps developers
from getting it wrong in the first place.
---
builtins/evalfile.c | 24 ++++++++++++++++++++++++
config.h.in | 3 +++
configure | 2 +-
configure.ac | 2 +-
shell.c | 31 +++++++++++++++++++++++++++++++
5 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/builtins/evalfile.c b/builtins/evalfile.c
index eb51c27..ed031d6 100644
--- a/builtins/evalfile.c
+++ b/builtins/evalfile.c
@@ -32,6 +32,10 @@
#include <signal.h>
#include <errno.h>
+#if defined (HAVE_SYS_STATVFS_H)
+# include <sys/statvfs.h>
+#endif
+
#include "../bashansi.h"
#include "../bashintl.h"
@@ -160,6 +164,26 @@ file_error_and_exit:
return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
}
+#if defined (HAVE_SYS_STATVFS_H) && defined (ST_NOEXEC)
+ /* If the script is loaded from a noexec mount point, throw an error. */
+ {
+ struct statvfs stvfs;
+
+ if (fstatvfs (fd, &stvfs) == -1)
+ {
+ close (fd);
+ goto file_error_and_exit;
+ }
+
+ if (stvfs.f_flag & ST_NOEXEC)
+ {
+ close (fd);
+ errno = EACCES;
+ goto file_error_and_exit;
+ }
+ }
+#endif
+
if (S_ISREG (finfo.st_mode) && file_size <= SSIZE_MAX)
{
string = (char *)xmalloc (1 + file_size);
diff --git a/config.h.in b/config.h.in
index 894892f..b16f1d6 100644
--- a/config.h.in
+++ b/config.h.in
@@ -1039,6 +1039,9 @@
/* Define if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
+/* Define if you have <sys/statvfs.h>. */
+#undef HAVE_SYS_STATVFS_H
+
/* Define if you have the <sys/stream.h> header file. */
#undef HAVE_SYS_STREAM_H
diff --git a/configure b/configure
index 52f6f5c..061b15e 100755
--- a/configure
+++ b/configure
@@ -9301,7 +9301,7 @@ fi
done
for ac_header in sys/pte.h sys/stream.h sys/select.h sys/file.h sys/ioctl.h \
- sys/param.h sys/socket.h sys/stat.h \
+ sys/param.h sys/socket.h sys/stat.h sys/statvfs.h \
sys/time.h sys/times.h sys/types.h sys/wait.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
diff --git a/configure.ac b/configure.ac
index f0d4aee..81b2a7c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -717,7 +717,7 @@ AC_CHECK_HEADERS(unistd.h stdlib.h stdarg.h varargs.h
limits.h string.h \
stdbool.h stddef.h stdint.h netdb.h pwd.h grp.h strings.h \
regex.h syslog.h ulimit.h)
AC_CHECK_HEADERS(sys/pte.h sys/stream.h sys/select.h sys/file.h sys/ioctl.h \
- sys/param.h sys/socket.h sys/stat.h \
+ sys/param.h sys/socket.h sys/stat.h sys/statvfs.h \
sys/time.h sys/times.h sys/types.h sys/wait.h)
AC_CHECK_HEADERS(netinet/in.h arpa/inet.h)
diff --git a/shell.c b/shell.c
index 0e47cf4..4739a31 100644
--- a/shell.c
+++ b/shell.c
@@ -46,6 +46,10 @@
# include <unistd.h>
#endif
+#if defined (HAVE_SYS_STATVFS_H)
+# include <sys/statvfs.h>
+#endif
+
#include "bashintl.h"
#define NEED_SH_SETLINEBUF_DECL /* used in externs.h */
@@ -334,6 +338,8 @@ static void shell_reinitialize __P((void));
static void show_shell_usage __P((FILE *, int));
+static void check_noexec __P((int, const char *));
+
#ifdef __CYGWIN__
static void
_cygwin32_check_tmp ()
@@ -717,6 +723,7 @@ main (argc, argv, env)
{
/* In this mode, bash is reading a script from stdin, which is a
pipe or redirected file. */
+ check_noexec (0, "stdin");
#if defined (BUFFERED_INPUT)
default_buffered_input = fileno (stdin); /* == 0 */
#else
@@ -1442,6 +1449,28 @@ start_debugger ()
#endif
}
+static void
+check_noexec (int fd, const char *filename)
+{
+#if defined (HAVE_SYS_STATVFS_H) && defined (ST_NOEXEC)
+ /* Make sure the file isn't on a noexec mount point. */
+ struct statvfs stvfs;
+
+ if (fstatvfs (fd, &stvfs) == -1)
+ {
+ file_error (filename);
+ exit (EX_NOTFOUND);
+ }
+
+ if (stvfs.f_flag & ST_NOEXEC)
+ {
+ errno = EACCES;
+ file_error (filename);
+ exit (EX_NOEXEC);
+ }
+#endif
+}
+
static int
open_shell_script (script_name)
char *script_name;
@@ -1579,6 +1608,8 @@ open_shell_script (script_name)
SET_CLOSE_ON_EXEC (fileno (default_input));
#endif /* !BUFFERED_INPUT */
+ check_noexec (fd, filename);
+
/* Just about the only way for this code to be executed is if something
like `bash -i /dev/stdin' is executed. */
if (interactive_shell && fd_is_tty)
--
2.6.2