sh: Allow more scripts without #!

Austin Group bugs #1226 and #1250 changed the requirements for shell scripts
without #! (POSIX does not specify #!; this is about the shell execution
when execve(2) returns an [ENOEXEC] error).

POSIX says we shall allow execution if the initial part intended to be
parsed by the shell consists of characters and does not contain the NUL
character.  This allows concatenating a shell script (ending with exec or
exit) and a binary payload.

In order to reject common binary files such as PNG images, check that there
is a lowercase letter or expansion before the last newline before the NUL
character, in addition to the check for the newline character suggested by
POSIX.
This commit is contained in:
Jilles Tjoelker 2020-05-30 16:00:49 +00:00
parent 51cefda170
commit e0f5c1387d
3 changed files with 42 additions and 1 deletions

View File

@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$");
#include <fcntl.h>
#include <errno.h>
#include <paths.h>
#include <stdbool.h>
#include <stdlib.h>
/*
@ -140,6 +141,37 @@ shellexec(char **argv, char **envp, const char *path, int idx)
}
static bool
isbinary(const char *data, size_t len)
{
const char *nul, *p;
bool hasletter;
nul = memchr(data, '\0', len);
if (nul == NULL)
return false;
/*
* POSIX says we shall allow execution if the initial part intended
* to be parsed by the shell consists of characters and does not
* contain the NUL character. This allows concatenating a shell
* script (ending with exec or exit) and a binary payload.
*
* In order to reject common binary files such as PNG images, check
* that there is a lowercase letter or expansion before the last
* newline before the NUL character, in addition to the check for
* the newline character suggested by POSIX.
*/
hasletter = false;
for (p = data; *p != '\0'; p++) {
if ((*p >= 'a' && *p <= 'z') || *p == '$' || *p == '`')
hasletter = true;
if (hasletter && *p == '\n')
return false;
}
return true;
}
static void
tryexec(char *cmd, char **argv, char **envp)
{
@ -155,7 +187,7 @@ tryexec(char *cmd, char **argv, char **envp)
if (in != -1) {
n = pread(in, buf, sizeof buf, 0);
close(in);
if (n > 0 && memchr(buf, '\0', n) != NULL) {
if (n > 0 && isbinary(buf, n)) {
errno = ENOEXEC;
return;
}

View File

@ -59,6 +59,7 @@ ${PACKAGE}FILES+= shellproc2.0
${PACKAGE}FILES+= shellproc3.0
${PACKAGE}FILES+= shellproc4.0
${PACKAGE}FILES+= shellproc5.0
${PACKAGE}FILES+= shellproc6.0
${PACKAGE}FILES+= subshell1.0 subshell1.0.stdout
${PACKAGE}FILES+= subshell2.0
${PACKAGE}FILES+= subshell3.0

View File

@ -0,0 +1,8 @@
# $FreeBSD$
T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit
trap 'rm -rf "${T}"' 0
printf 'printf "this "\necho is a test\nexit\n\0' >"$T/testshellproc"
chmod 755 "$T/testshellproc"
PATH=$T:$PATH
[ "`testshellproc`" = "this is a test" ]