Skip to content

Commit e0f5c13

Browse files
committed
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.
1 parent 51cefda commit e0f5c13

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

bin/sh/exec.c

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$");
4444
#include <fcntl.h>
4545
#include <errno.h>
4646
#include <paths.h>
47+
#include <stdbool.h>
4748
#include <stdlib.h>
4849

4950
/*
@@ -140,6 +141,37 @@ shellexec(char **argv, char **envp, const char *path, int idx)
140141
}
141142

142143

144+
static bool
145+
isbinary(const char *data, size_t len)
146+
{
147+
const char *nul, *p;
148+
bool hasletter;
149+
150+
nul = memchr(data, '\0', len);
151+
if (nul == NULL)
152+
return false;
153+
/*
154+
* POSIX says we shall allow execution if the initial part intended
155+
* to be parsed by the shell consists of characters and does not
156+
* contain the NUL character. This allows concatenating a shell
157+
* script (ending with exec or exit) and a binary payload.
158+
*
159+
* In order to reject common binary files such as PNG images, check
160+
* that there is a lowercase letter or expansion before the last
161+
* newline before the NUL character, in addition to the check for
162+
* the newline character suggested by POSIX.
163+
*/
164+
hasletter = false;
165+
for (p = data; *p != '\0'; p++) {
166+
if ((*p >= 'a' && *p <= 'z') || *p == '$' || *p == '`')
167+
hasletter = true;
168+
if (hasletter && *p == '\n')
169+
return false;
170+
}
171+
return true;
172+
}
173+
174+
143175
static void
144176
tryexec(char *cmd, char **argv, char **envp)
145177
{
@@ -155,7 +187,7 @@ tryexec(char *cmd, char **argv, char **envp)
155187
if (in != -1) {
156188
n = pread(in, buf, sizeof buf, 0);
157189
close(in);
158-
if (n > 0 && memchr(buf, '\0', n) != NULL) {
190+
if (n > 0 && isbinary(buf, n)) {
159191
errno = ENOEXEC;
160192
return;
161193
}

bin/sh/tests/execution/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ ${PACKAGE}FILES+= shellproc2.0
5959
${PACKAGE}FILES+= shellproc3.0
6060
${PACKAGE}FILES+= shellproc4.0
6161
${PACKAGE}FILES+= shellproc5.0
62+
${PACKAGE}FILES+= shellproc6.0
6263
${PACKAGE}FILES+= subshell1.0 subshell1.0.stdout
6364
${PACKAGE}FILES+= subshell2.0
6465
${PACKAGE}FILES+= subshell3.0

bin/sh/tests/execution/shellproc6.0

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# $FreeBSD$
2+
3+
T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit
4+
trap 'rm -rf "${T}"' 0
5+
printf 'printf "this "\necho is a test\nexit\n\0' >"$T/testshellproc"
6+
chmod 755 "$T/testshellproc"
7+
PATH=$T:$PATH
8+
[ "`testshellproc`" = "this is a test" ]

0 commit comments

Comments
 (0)