From bd0aeed0626aa2f9785ba52aaaaf9759e9976e80 Mon Sep 17 00:00:00 2001
From: Assaf Gordon
Date: Sun, 24 Apr 2016 21:06:57 -0400
Subject: [PATCH] sed: new --sandbox option
In sandbox mode, r/w/e commands are not allowed.
This ensures sed operates only on files designated on the command line,
and cannot execute external programs.
* sed/sed.c (sandbox): New option variable.
(usage): List new option.
(main): Accept new option in getopt.
* sed/sed.h (sandbox): New extern option variable.
* sed/compile.c (DISALLOWED_CMD): New error message.
(read_filename): Abort in sandbox mode (r/w commands).
(compile_program): Abort on 'e' in sandbox mode.
* testsuite/sandbox.sh: Test new option.
* testsuite/local.mk: Add new test.
* NEWS: Mention new option.
* doc/sed-in.texi, doc/sed.texi: Document new option.
---
NEWS | 5 +++
doc/sed-in.texi | 9 +++++
doc/sed.texi | 9 +++++
sed/compile.c | 17 ++++++++--
sed/sed.c | 13 ++++++++
sed/sed.h | 3 ++
testsuite/local.mk | 1 +
testsuite/sandbox.sh | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 147 insertions(+), 2 deletions(-)
create mode 100755 testsuite/sandbox.sh
diff --git a/NEWS b/NEWS
index ab11060..e9b4584 100644
--- a/NEWS
+++ b/NEWS
@@ -82,6 +82,11 @@ GNU sed NEWS -*- outline -*-
diffutils and parted for a few years.
+** New Features
+
+ new --sandbox option rejects programs with r/w/e commands.
+
+
* Noteworthy changes in release 4.2.2 (2012-12-22) [stable]
* don't misbehave (truncate input) for lines of length 2^31 and longer
diff --git a/doc/sed-in.texi b/doc/sed-in.texi
index 1c5e0b2..b5f0033 100644
--- a/doc/sed-in.texi
+++ b/doc/sed-in.texi
@@ -367,6 +367,15 @@ of each file, @code{$} refers to the last line of each file,
and files invoked from the @code{R} commands are rewound at the
start of each file.
address@hidden --sandbox
address@hidden --sandbox
address@hidden Sandbox mode
+In sandbox mode, @code{e/w/r} commands are rejected - programs containing
+them will be aborted without being run. Sandbox mode ensures @command{sed}
+operates only on the input files designated on the command line, and
+cannot run external programs.
+
+
@item -u
@itemx --unbuffered
@opindex -u
diff --git a/doc/sed.texi b/doc/sed.texi
index bf45cfc..fc2afac 100644
--- a/doc/sed.texi
+++ b/doc/sed.texi
@@ -368,6 +368,15 @@ of each file, @code{$} refers to the last line of each file,
and files invoked from the @code{R} commands are rewound at the
start of each file.
address@hidden --sandbox
address@hidden --sandbox
address@hidden Sandbox mode
+In sandbox mode, @code{e/w/r} commands are rejected - programs containing
+them will be aborted without being run. Sandbox mode ensures @command{sed}
+operates only on the input files designated on the command line, and
+cannot run external programs.
+
+
@item -u
@itemx --unbuffered
@opindex -u
diff --git a/sed/compile.c b/sed/compile.c
index 36bdec6..2d5c4b1 100644
--- a/sed/compile.c
+++ b/sed/compile.c
@@ -139,7 +139,8 @@ static const char errors[] =
"unknown command: `%c'\0"
"incomplete command\0"
"\":\" lacks a label\0"
- "recursive escaping after \\c not allowed";
+ "recursive escaping after \\c not allowed\0"
+ "e/r/w commands disabled in sandbox mode";
#define BAD_BANG (errors)
#define BAD_COMMA (BAD_BANG + sizeof(N_("multiple `!'s")))
@@ -185,7 +186,10 @@ static const char errors[] =
+ sizeof(N_("incomplete command")))
#define RECURSIVE_ESCAPE_C (COLON_LACKS_LABEL \
+ sizeof(N_("\":\" lacks a label")))
-/* #define END_ERRORS (COLON_LACKS_LABEL + sizeof(N_("\":\" lacks a label"))) */
+#define DISALLOWED_CMD (RECURSIVE_ESCAPE_C \
+ + sizeof(N_("recursive escaping after \\c not allowed")))
+/* #define END_ERRORS (DISALLOWED_CMD \
+ + sizeof(N_( "e/r/w commands disabled in sandbox mode"))) */
static struct output *file_read = NULL;
static struct output *file_write = NULL;
@@ -340,6 +344,9 @@ read_filename(void)
struct buffer *b;
int ch;
+ if (sandbox)
+ bad_prog(_(DISALLOWED_CMD));
+
b = init_buffer();
ch = in_nonblank();
while (ch != EOF && ch != '\n')
@@ -1106,6 +1113,9 @@ compile_program(struct vector *vector)
break;
case 'e':
+ if (sandbox)
+ bad_prog(_(DISALLOWED_CMD));
+
ch = in_nonblank();
if (ch == EOF || ch == '\n')
{
@@ -1235,6 +1245,9 @@ compile_program(struct vector *vector)
cur_cmd->x.cmd_subst->regx =
compile_regex(b, flags, cur_cmd->x.cmd_subst->max_id + 1);
free_buffer(b);
+
+ if (cur_cmd->x.cmd_subst->eval && sandbox)
+ bad_prog(_(DISALLOWED_CMD));
}
break;
diff --git a/sed/sed.c b/sed/sed.c
index 44efaab..ed11049 100644
--- a/sed/sed.c
+++ b/sed/sed.c
@@ -19,6 +19,7 @@
#include "sed.h"
+#include
#include
#include
#include
@@ -53,6 +54,9 @@ bool separate_files = false;
/* If set, follow symlinks when processing in place */
bool follow_symlinks = false;
+/* If set, opearate in 'sandbox' mode */
+bool sandbox = false;
+
/* How do we edit files in-place? (we don't if NULL) */
char *in_place_extension = NULL;
@@ -161,6 +165,8 @@ Usage: %s [OPTION]... {script-only-if-no-other-script} [input-file]...\n\
fprintf(out, _(" -s, --separate\n\
consider files as separate rather than as a single,\n\
continuous long stream.\n"));
+ fprintf(out, _(" --sandbox\n\
+ operate in sandbox mode.\n"));
fprintf(out, _(" -u, --unbuffered\n\
load minimal amounts of data from the input files and flush\n\
the output buffers more often\n"));
@@ -189,6 +195,8 @@ main (int argc, char **argv)
#define SHORTOPTS "bsnrzuEe:f:l:i::V:"
#endif
+ enum { SANDBOX_OPTION = CHAR_MAX+1 };
+
static const struct option longopts[] = {
{"binary", 0, NULL, 'b'},
{"regexp-extended", 0, NULL, 'r'},
@@ -204,6 +212,7 @@ main (int argc, char **argv)
{"quiet", 0, NULL, 'n'},
{"posix", 0, NULL, 'p'},
{"silent", 0, NULL, 'n'},
+ {"sandbox", 0, NULL, SANDBOX_OPTION},
{"separate", 0, NULL, 's'},
{"unbuffered", 0, NULL, 'u'},
{"version", 0, NULL, 'v'},
@@ -329,6 +338,10 @@ main (int argc, char **argv)
separate_files = true;
break;
+ case SANDBOX_OPTION:
+ sandbox = true;
+ break;
+
case 'u':
unbuffered = true;
break;
diff --git a/sed/sed.h b/sed/sed.h
index 5c249cf..8579a86 100644
--- a/sed/sed.h
+++ b/sed/sed.h
@@ -240,6 +240,9 @@ extern bool use_extended_syntax_p;
extern int mb_cur_max;
extern bool is_utf8;
+/* If set, operate in 'sandbox' mode - disable e/r/w commands */
+extern bool sandbox;
+
#define MBRTOWC(pwc, s, n, ps) \
(mb_cur_max == 1 ? \
(*(pwc) = btowc (*(unsigned char *) (s)), 1) : \
diff --git a/testsuite/local.mk b/testsuite/local.mk
index e274d46..6ae11e4 100644
--- a/testsuite/local.mk
+++ b/testsuite/local.mk
@@ -49,6 +49,7 @@ T = \
testsuite/range-overlap.sh \
testsuite/recursive-escape-c.sh \
testsuite/regex-errors.sh \
+ testsuite/sandbox.sh \
testsuite/stdin-prog.sh \
testsuite/subst-options.sh \
testsuite/subst-mb-incomplete.sh \
diff --git a/testsuite/sandbox.sh b/testsuite/sandbox.sh
new file mode 100755
index 0000000..3e529fe
--- /dev/null
+++ b/testsuite/sandbox.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+# Test --sandbox mode
+
+# Copyright (C) 2016 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+. "${srcdir=.}/testsuite/init.sh"; path_prepend_ ./sed
+print_ver_ sed
+
+echo a > a || framework_failure_
+echo b > b || framework_failure_
+
+# Same error message, different character position in the sed program.
+for i in 1 6 14 ; do
+ err="sed: -e expression #1, char $i: e/r/w commands disabled in sandbox mode"
+ echo "$err" > exp-err$i || framework_failure_
+done
+
+fail=0
+
+# read command - without sandbox
+printf "a\nb\n" > exp || framework_failure_
+sed rb a > out || fail=1
+compare exp out || fail=1
+
+# read command - with sandbox
+returns_ 1 sed --sandbox -e 'ra' b >/dev/null 2>err1 || fail=1
+compare exp-err1 err1 || fail=1
+
+
+# write command (create file 'c') - without sandbox
+sed wc a > out || fail=1
+compare a c || fail=1
+compare out a || fail=1
+
+# write command - with sandbox
+returns_ 1 sed --sandbox -e 'wd' a >/dev/null 2>err2 || fail=1
+compare exp-err1 err1 || fail=1
+# ensure file 'd' was not created
+test -e d && fail=1
+
+
+
+# execute command - without sandbox
+sed 'etouch e' b > out || fail=1
+compare b out || fail=1
+# ensure 'e' was created
+test -e e || fail=1
+
+# execute command - with sandbox
+returns_ 1 sed --sandbox -e 'etouch f' b >/dev/null 2>err3 || fail=1
+compare exp-err1 err3 || fail=1
+# ensure 'f' was not created
+test -e f && fail=1
+
+
+
+# substitute+write option - without sandbox
+sed 's/^//wg' a > out || fail=1
+test -e g || fail=1
+
+# substitute+write option - with sandbox
+returns_ 1 sed --sandbox 's/^//wh' a >/dev/null 2>err4 || fail=1
+compare exp-err6 err4 || fail=1
+# ensure file 'h' was not created
+test -e h && fail=1
+
+
+
+# substitute+execute option - without sandbox
+sed 's/.*/touch i/e' a > out || fail=1
+test -e i || fail=1
+
+# substitute+execute option - with sandbox
+returns_ 1 sed --sandbox 's/.*/touch j/e' a >/dev/null 2>err5 || fail=1
+compare exp-err14 err5 || fail=1
+# ensure file 'j' was not created
+test -e j && fail=1
+
+
+Exit $fail
--
2.8.2