aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Bennett <steveb@workware.net.au>2011-05-18 08:24:47 +1000
committerSteve Bennett <steveb@workware.net.au>2025-07-16 09:34:08 +1000
commitf72a03487ec28328678d48472026877e7d66bc29 (patch)
tree97a8a5d3f0dd30100f8d7ca958fb03abb5f56a74
parentee3d7dc3283b6aa37332a69354f5d98eaea055dd (diff)
downloadjimtcl-f72a03487ec28328678d48472026877e7d66bc29.zip
jimtcl-f72a03487ec28328678d48472026877e7d66bc29.tar.gz
jimtcl-f72a03487ec28328678d48472026877e7d66bc29.tar.bz2
core: taint support
See README.taint Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r--README.taint143
-rw-r--r--auto.def8
-rw-r--r--examples/udp.client5
-rw-r--r--examples/udp.server6
-rw-r--r--jim-aio.c171
-rw-r--r--jim-exec.c5
-rw-r--r--jim-file.c9
-rw-r--r--jim-load.c5
-rw-r--r--jim-package.c12
-rw-r--r--jim-subcmd.c12
-rw-r--r--jim-subcmd.h4
-rw-r--r--jim.c211
-rw-r--r--jim.h27
-rw-r--r--tests/debug.test4
-rw-r--r--tests/socket.test9
-rw-r--r--tests/ssl.test4
-rw-r--r--tests/taint.test204
17 files changed, 798 insertions, 41 deletions
diff --git a/README.taint b/README.taint
new file mode 100644
index 0000000..86ba697
--- /dev/null
+++ b/README.taint
@@ -0,0 +1,143 @@
+Taint Suport for Jim Tcl
+========================
+
+Author: Steve Bennett <steveb@workware.net.au>
+Date: 24 May 2011
+
+OVERVIEW
+--------
+Perl and Ruby support the concept of tainted data, taint sources
+and taint sinks. The idea is to improve security in situations
+where data may be coming from outside the program (e.g. input
+to a web application) should not inadvertently be output
+on a web page unescaped (to avoid XSS attacks), to a database
+(to avoid SQL injections attacks) or to execute system commands
+(to avoid system attacks).
+
+Standard Tcl does not support tainting. Instead it uses "safe"
+interpreters for a similar purpose. For Jim Tcl, taint support is
+smaller and simpler.
+
+HOW IT WORKS
+------------
+
+Any data read from a 'taint source' is considered tainted.
+As that data moves through the system, it retains its taint property.
+Tainted data should not be allowed to be consumed by a 'taint sink'.
+An error should be raised instead.
+
+Taint Sources
+~~~~~~~~~~~~~
+Untrusted data may come from various sources in the system.
+In Jim Tcl, the sources of external data are:
+
+* Data read file a file or socket (aio read, gets, recvfrom)
+* Command line arguments ($argv)
+* Loaded code or scripts (source, package require, load)
+* Environment variables (env)
+* Custom Tcl commands implemented as C code
+
+Any data from these sources may be considered "tainted".
+
+By default, sockets are considered taint sources while the other
+external data sources are not. Data read from a 'taint source'
+filehandle with read, recvfrom or gets is tainted.
+
+No filehandles are considered taint sinks by default.
+
+Client sockets produced by accept inherit the accept socket settings.
+
+Taint Sinks
+~~~~~~~~~~~
+In order for tainted data to cause security problems, the data
+need to be used in certain contexts. These *may* include:
+
+* Establishing network connections (socket)
+* Sending the data to certain file descriptors (aio puts, sendto)
+* Modifying the filesystem (open, file delete, rename, mkdir)
+* Running commands (exec)
+* Evaluating scripts (eval, uplevel, source)
+* Use in custom Tcl commands implemented as C code
+
+Taint Propagation
+~~~~~~~~~~~~~~~~~
+As tainted data is assigned, or manipulated, it should retain
+its taint property. This includes the creation of new values
+based on tainted data. Jim Tcl takes a conservative approach
+to taint propagation as follows:
+
+* Any copy of a tainted value is tainted (e.g. set, proc calls)
+* Any value constructed in part from a tainted value is tainted
+ (append, lappend, lset)
+* A tainted value added to a container (dict, list, array) remains tainted.
+ While the tainted value can be distinguished from other values
+ in the container, the container is not tainted. However if the container
+ needs to change representation (the entire container becomes tainted.
+* Integer and floating point values are not tainted
+
+Taint types
+-----------
+It may be useful to distinguish between different types of taint.
+Each taint type is associate with a bit field. The standard taint
+type is 1, but taint types 2, 4, etc. may also be used. If a taint
+source is marked as taint type 2, it will not be flagged as invalid
+when consumed by a taint sink marked as taint type 4.
+
+The commands exec, source, etc. consider any taint to be invalid, however
+file descriptors may have specific taint source and sink types specified.
+
+Taint-aware Commands
+--------------------
+The following commands will fail if any argument is tainted:
+
+- source, exec, open, socket, load, file mkdir, file delete, file rename
+
+In addition, 'package require' will ignore any tainted paths in $auto_path
+
+HOW TO USE IT
+-------------
+To mark a value as tainted:
+
+ taint varname
+
+To remove the taint from a value:
+
+ untaint varname
+
+To determine if a value is tainted:
+
+ info tainted $var
+
+To mark a filehandle as a taint source or sink (or not):
+
+ $aiohandle taint source|sink ?0|n?
+
+More Information
+----------------
+In order to simplify taint propagation, the interpreter
+examines the arguments to every command (plus the command itself).
+If any argument is tainted, the command execution is considered tainted.
+Any new objects (except int and double) created during the execution of the command
+will be marked tainted.
+
+The Rules
+---------
+- The taint and untaint commands operate on variables and taint/untaint the contents of the variable
+- Adding/modifying a list/dict/array element taints that element plus the "container" but not
+ the other elements in that container
+- Tainting a container element taints the container too
+- Untainting a container element does not untaint the container, even if it contains no more tainted elements
+- Tainting or untainting a container taints or untaints all elements in the container
+- Any element of $auto_path that is tainted will be ignored when loading packages with package require
+
+Specific Notes
+--------------
+In general, a conservative approach is used to tainting, so if
+a command creates a new object while any of it's arguments are tainted,
+the new object is also tainted.
+
+However the list-related commands are more intelligent.
+All list-related commands such as lindex, lrange, lassign and lreplace will
+maintain the taint of existing list elements, but will avoid tainting untainted elements.
+For example, if the list {a b t d} contains one tainted element, 't', then [lreverse $a]
+will produce a list with only one tainted element.
diff --git a/auto.def b/auto.def
index 81ee889..1afb7d3 100644
--- a/auto.def
+++ b/auto.def
@@ -28,6 +28,7 @@ options {
full
allextmod => "Enable all non-default extensions as modules if prerequisites are found"
compat => "Enable some backward compatibility behaviour"
+ taint=1 => "Disable taint support"
extinfo => "Show information about available extensions"
with-jim-shared shared => "Build a shared library instead of a static library"
jim-regexp=1 => "Prefer POSIX regex if over the the built-in (Tcl-compatible) regex"
@@ -482,10 +483,15 @@ if {[opt-bool compat]} {
msg-result "Enabling compatibility mode"
define JIM_COMPAT
}
+
if {![opt-bool introspection]} {
msg-result "Disabling introspection"
define JIM_NO_INTROSPECTION
}
+if {[opt-bool taint]} {
+ msg-result "Enabling taint support"
+ define JIM_TAINT
+}
if {[opt-bool shared with-jim-shared]} {
msg-result "Building shared library"
} else {
@@ -663,7 +669,7 @@ foreach mod $extinfo(module-c) {
}
define BUILD_SHOBJS [join $lines \n]
-make-config-header jim-config.h -auto {HAVE_LONG_LONG* JIM_UTF8 SIZEOF_INT} -bare JIM_VERSION -none *
+make-config-header jim-config.h -auto {HAVE_LONG_LONG* JIM_UTF8 JIM_TAINT SIZEOF_INT} -bare JIM_VERSION -none *
make-config-header jimautoconf.h -auto {jim_ext_* TCL_PLATFORM_* TCL_LIBRARY USE_* JIM_* _FILE_OFFSET*} -bare {S_I*}
make-template Makefile.in
make-template tests/Makefile.in
diff --git a/examples/udp.client b/examples/udp.client
index 9e9ac14..312bb46 100644
--- a/examples/udp.client
+++ b/examples/udp.client
@@ -26,3 +26,8 @@ foreach i [range 5 10] {
# Receive the response - max length of 100
puts [$s recvfrom 100]
}
+
+# If taint is supported, this is an unsafe command
+# and the server will refuse to do it
+$s puts -nonewline {[exec echo hello]}
+puts [$s recvfrom 100]
diff --git a/examples/udp.server b/examples/udp.server
index 03f41cd..cc026f0 100644
--- a/examples/udp.server
+++ b/examples/udp.server
@@ -3,6 +3,12 @@
# Listen on port 20000. No host specified means 0.0.0.0
set s [socket dgram.server 20000]
+# We won't run unsafe commands, but
+# we don't mind sending anything back
+catch {
+ $s taint sink 0
+}
+
# For each request...
$s readable {
# Get the request (max 80 chars) - need the source address
diff --git a/jim-aio.c b/jim-aio.c
index 221d7f5..4cb72f9 100644
--- a/jim-aio.c
+++ b/jim-aio.c
@@ -185,6 +185,8 @@ typedef struct AioFile
int flags; /* AIO_KEEPOPEN | AIO_NODELETE | AIO_EOF */
long timeout; /* timeout (in ms) for read operations if blocking */
int fd;
+ unsigned taintsource; /* Data read from the file are tainted with this value */
+ unsigned taintsink; /* Data with any of these taint types can't be written to this file */
int addr_family;
void *ssl;
const JimAioFopsType *fops;
@@ -380,6 +382,7 @@ static int aio_start_nonblocking(AioFile *af)
}
static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
+static void JimAioSetTaint(AioFile *af, int taintsource, int taintsink);
static AioFile *JimMakeChannel(Jim_Interp *interp, int fd, Jim_Obj *filename,
const char *hdlfmt, int family, int flags);
@@ -969,6 +972,7 @@ static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_ERR;
}
objPtr = aio_read_consume(interp, af, neededLen);
+ Jim_TaintObj(objPtr, af->taintsource);
aio_set_nonblocking(af, nb);
@@ -1017,6 +1021,30 @@ static int aio_cmd_getfd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_OK;
}
+static int aio_cmd_gettaint(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+
+ static const char * const options[] = { "-source", "-sink", NULL };
+ enum { OPT_SOURCE, OPT_SINK };
+ int option;
+
+ if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
+ return JIM_ERR;
+ }
+ switch (option) {
+ case OPT_SOURCE:
+ Jim_SetResultInt(interp, af->taintsource);
+ break;
+
+ case OPT_SINK:
+ Jim_SetResultInt(interp, af->taintsink);
+ break;
+ }
+
+ return JIM_OK;
+}
+
static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
AioFile *af = Jim_CmdPrivData(interp);
@@ -1024,6 +1052,7 @@ static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
jim_wide maxlen = JIM_WIDE_MAX;
int ok = 1;
Jim_Obj *objv[4];
+ long taintsink;
if (argc == 2) {
if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
@@ -1031,6 +1060,19 @@ static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
}
}
+ objv[0] = argv[0];
+ objv[1] = Jim_NewStringObj(interp, "gettaint", -1);
+ objv[2] = Jim_NewStringObj(interp, "-sink", -1);
+ if (Jim_EvalObjVector(interp, 3, objv) != JIM_OK || Jim_GetLong(interp, Jim_GetResult(interp), &taintsink) != JIM_OK) {
+ Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", argv[0]);
+ return JIM_ERR;
+ }
+
+ if (af->taintsource & taintsink) {
+ Jim_SetResultString(interp, "copying tainted source", -1);
+ return JIM_ERR;
+ }
+
/* Need to flush any write data first. This could fail because of send buf full,
* but more likely because the target isn't a filehandle.
* Should use use getfd to test for that case instead?
@@ -1134,6 +1176,7 @@ static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
else if (!objPtr) {
objPtr = Jim_NewStringObj(interp, NULL, 0);
}
+ Jim_TaintObj(objPtr, af->taintsource);
if (argc) {
if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
@@ -1164,6 +1207,11 @@ static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
int wnow = 0;
int nl = 1;
+ if (Jim_CheckTaint(interp, af->taintsink)) {
+ Jim_SetResultString(interp, "puts: tainted data", -1);
+ return JIM_ERR;
+ }
+
if (argc == 2) {
if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
return -1;
@@ -1236,6 +1284,7 @@ static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
AioFile *af = Jim_CmdPrivData(interp);
+ Jim_Obj *objPtr;
char *buf;
union sockaddr_any sa;
long len;
@@ -1255,7 +1304,10 @@ static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_ERR;
}
buf[rlen] = 0;
- Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
+
+ objPtr = Jim_NewStringObjNoAlloc(interp, buf, rlen);
+ Jim_TaintObj(objPtr, af->taintsource);
+ Jim_SetResult(interp, objPtr);
if (argc > 1) {
return JimSetVariableSocketAddress(interp, argv[1], &sa, salen);
@@ -1275,6 +1327,10 @@ static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
const char *addr = Jim_String(argv[1]);
socklen_t salen;
+ if (Jim_CheckTaint(interp, af->taintsink)) {
+ Jim_SetResultString(interp, "sendto: tainted data", -1);
+ return JIM_ERR;
+ }
if (JimParseSocketAddress(interp, af->addr_family, SOCK_DGRAM, addr, &sa, &salen) != JIM_OK) {
return JIM_ERR;
}
@@ -1297,7 +1353,8 @@ static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
- AioFile *af = Jim_CmdPrivData(interp);
+ AioFile *serv_af = Jim_CmdPrivData(interp);
+ AioFile *af;
int sock;
union sockaddr_any sa;
socklen_t salen = sizeof(sa);
@@ -1305,7 +1362,7 @@ static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
int n = 0;
int flags = AIO_NODELETE;
- sock = accept(af->fd, &sa.sa, &salen);
+ sock = accept(serv_af->fd, &sa.sa, &salen);
if (sock < 0) {
JimAioSetError(interp, NULL);
return JIM_ERR;
@@ -1330,8 +1387,13 @@ static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
}
/* Create the file command */
- return JimMakeChannel(interp, sock, filenameObj,
- "aio.sockstream%ld", af->addr_family, flags) ? JIM_OK : JIM_ERR;
+ af = JimMakeChannel(interp, sock, filenameObj,
+ "aio.sockstream%ld", serv_af->addr_family, flags);
+ if (af) {
+ JimAioSetTaint(af, serv_af->taintsource, serv_af->taintsink);
+ return JIM_OK;
+ }
+ return JIM_ERR;
}
static int aio_cmd_sockname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
@@ -1487,6 +1549,45 @@ static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_OK;
}
+#ifdef JIM_TAINT
+static int aio_cmd_taint(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ static const char * const types[] = {
+ "sink",
+ "source",
+ NULL
+ };
+ enum
+ {
+ TAINT_TYPE_SINK,
+ TAINT_TYPE_SOURCE,
+ };
+ int type;
+ long taint;
+
+ if (Jim_GetEnum(interp, argv[0], types, &type, NULL, JIM_ERRMSG) != JIM_OK)
+ return JIM_ERR;
+
+ if (argc == 1) {
+ Jim_SetResultInt(interp, type == TAINT_TYPE_SINK ? af->taintsink : af->taintsource);
+ return JIM_OK;
+ }
+ else if (Jim_GetLong(interp, argv[1], &taint) == JIM_OK) {
+ if (type == TAINT_TYPE_SINK) {
+ af->taintsink = taint;
+ }
+ else {
+ af->taintsource = taint;
+ }
+ return JIM_OK;
+ }
+ else {
+ return JIM_ERR;
+ }
+}
+#endif
+
#ifdef O_NDELAY
static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
@@ -2058,8 +2159,17 @@ static const jim_subcmd_type aio_command_table[] = {
aio_cmd_getfd,
0,
0,
+ JIM_MODFLAG_HIDDEN,
/* Description: Internal command to return the underlying file descriptor. */
},
+ { "gettaint",
+ "?-source|-sink?",
+ aio_cmd_gettaint,
+ 1,
+ 1,
+ JIM_MODFLAG_HIDDEN,
+ /* Description: Internal command to return the taint of the channel. */
+ },
{ "gets",
"?var?",
aio_cmd_gets,
@@ -2175,6 +2285,15 @@ static const jim_subcmd_type aio_command_table[] = {
0,
/* Description: Returns the original filename */
},
+#ifdef JIM_TAINT
+ { "taint",
+ "source|sink ?0|n?",
+ aio_cmd_taint,
+ 1,
+ 2,
+ /* Description: Set or return the taint setting */
+ },
+#endif
#ifdef O_NDELAY
{ "ndelay",
"?0|1?",
@@ -2413,6 +2532,7 @@ static int JimAioOpenCommand(Jim_Interp *interp, int argc,
Jim_Obj *const *argv)
{
int openflags;
+ AioFile *af;
const char *filename;
int fd = -1;
int n = 0;
@@ -2424,6 +2544,10 @@ static int JimAioOpenCommand(Jim_Interp *interp, int argc,
}
if (argc < 2 || argc > 3 + n) {
Jim_WrongNumArgs(interp, 1, argv, "filename ?-noclose? ?mode?");
+ }
+
+ if (Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ Jim_SetTaintError(interp, 1, argv);
return JIM_ERR;
}
@@ -2456,13 +2580,20 @@ static int JimAioOpenCommand(Jim_Interp *interp, int argc,
else {
openflags = O_RDONLY;
}
+
fd = open(filename, openflags, 0666);
if (fd < 0) {
JimAioSetError(interp, argv[1]);
return JIM_ERR;
}
- return JimMakeChannel(interp, fd, argv[1], "aio.handle%ld", 0, flags) ? JIM_OK : JIM_ERR;
+ af = JimMakeChannel(interp, fd, argv[1], "aio.handle%ld", 0, flags);
+ if (af) {
+ /* filehandles created by open are not tainted by default */
+ JimAioSetTaint(af, 0, 0);
+ return JIM_OK;
+ }
+ return JIM_ERR;
}
#if defined(JIM_SSL) && !defined(JIM_BOOTSTRAP)
@@ -2494,6 +2625,12 @@ static SSL_CTX *JimAioSslCtx(Jim_Interp *interp)
}
#endif /* JIM_BOOTSTRAP */
+static void JimAioSetTaint(AioFile *af, int taintsource, int taintsink)
+{
+ af->taintsource = taintsource;
+ af->taintsink = taintsink;
+}
+
/**
* Creates a channel for fd/filename.
*
@@ -2553,6 +2690,8 @@ static AioFile *JimMakeChannel(Jim_Interp *interp, int fd, Jim_Obj *filename,
af->rbuf_len = AIO_DEFAULT_RBUF_LEN;
/* Don't allocate rbuf or readbuf until we need it */
+ /* By default, all channels are JIM_TAINT_STD for input and output. */
+ JimAioSetTaint(af, JIM_TAINT_STD, JIM_TAINT_STD);
Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
/* Note that the command must use the global namespace, even if
@@ -2688,6 +2827,11 @@ static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return Jim_CheckShowCommands(interp, argv[1], socktypes);
}
+ if (Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ Jim_SetTaintError(interp, 1, argv);
+ return JIM_ERR;
+ }
+
while (argc > 1 && Jim_String(argv[1])[0] == '-') {
static const char * const options[] = { "-async", "-ipv6", "-noclose", NULL };
enum { OPT_ASYNC, OPT_IPV6, OPT_NOCLOSE };
@@ -2927,8 +3071,17 @@ static int JimAioLoadSSLCertsCommand(Jim_Interp *interp, int argc, Jim_Obj *cons
}
#endif /* JIM_BOOTSTRAP */
+/* Create filehandles for stdin, stdout and stderr */
+static void JimMakeStdioChannel(Jim_Interp *interp, FILE *fh, const char *name, unsigned flags)
+{
+ /* Note: this can't fail */
+ AioFile *af = JimMakeChannel(interp, fileno(fh), NULL, name, 0, AIO_KEEPOPEN | flags);
+ JimAioSetTaint(af, 0, 0);
+}
+
int Jim_aioInit(Jim_Interp *interp)
{
+
if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
return JIM_ERR;
@@ -2945,9 +3098,9 @@ int Jim_aioInit(Jim_Interp *interp)
#endif
/* Create filehandles for stdin, stdout and stderr */
- JimMakeChannel(interp, fileno(stdin), NULL, "stdin", 0, AIO_KEEPOPEN);
- JimMakeChannel(interp, fileno(stdout), NULL, "stdout", 0, AIO_KEEPOPEN);
- JimMakeChannel(interp, fileno(stderr), NULL, "stderr", 0, AIO_KEEPOPEN | AIO_WBUF_NONE);
+ JimMakeStdioChannel(interp, stdin, "stdin", 0);
+ JimMakeStdioChannel(interp, stdout, "stdout", 0);
+ JimMakeStdioChannel(interp, stderr, "stderr", AIO_WBUF_NONE);
return JIM_OK;
}
diff --git a/jim-exec.c b/jim-exec.c
index bbd2deb..bcaf050 100644
--- a/jim-exec.c
+++ b/jim-exec.c
@@ -374,6 +374,11 @@ static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
Jim_Obj *errStrObj;
struct WaitInfoTable *table = Jim_CmdPrivData(interp);
+ if (Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ Jim_SetTaintError(interp, 1, argv);
+ return JIM_ERR;
+ }
+
/*
* See if the command is to be run in the background; if so, create
* the command, detach it, and return.
diff --git a/jim-file.c b/jim-file.c
index 01e305a..0d95511 100644
--- a/jim-file.c
+++ b/jim-file.c
@@ -999,6 +999,7 @@ static const jim_subcmd_type file_command_table[] = {
file_cmd_delete,
1,
-1,
+ JIM_MODFLAG_NOTAINT,
/* Description: Deletes the files or directories (must be empty unless -force) */
},
{ "mkdir",
@@ -1006,6 +1007,7 @@ static const jim_subcmd_type file_command_table[] = {
file_cmd_mkdir,
1,
-1,
+ JIM_MODFLAG_NOTAINT,
/* Description: Creates the directories */
},
{ "tempfile",
@@ -1013,6 +1015,7 @@ static const jim_subcmd_type file_command_table[] = {
file_cmd_tempfile,
0,
1,
+ JIM_MODFLAG_NOTAINT,
/* Description: Creates a temporary filename */
},
{ "rename",
@@ -1020,6 +1023,7 @@ static const jim_subcmd_type file_command_table[] = {
file_cmd_rename,
2,
3,
+ JIM_MODFLAG_NOTAINT,
/* Description: Renames a file */
},
#if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
@@ -1100,6 +1104,11 @@ static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
const char *path;
+ if (Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ Jim_SetTaintError(interp, 1, argv);
+ return JIM_ERR;
+ }
+
if (argc != 2) {
Jim_WrongNumArgs(interp, 1, argv, "dirname");
return JIM_ERR;
diff --git a/jim-load.c b/jim-load.c
index f3f23d0..cfe626e 100644
--- a/jim-load.c
+++ b/jim-load.c
@@ -119,6 +119,11 @@ void Jim_FreeLoadHandles(Jim_Interp *interp)
/* [load] */
static int Jim_LoadCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
+ if (Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ Jim_SetTaintError(interp, 1, argv);
+ return JIM_ERR;
+ }
+
if (argc < 2) {
Jim_WrongNumArgs(interp, 1, argv, "libraryFile");
return JIM_ERR;
diff --git a/jim-package.c b/jim-package.c
index 18b64fe..7471e80 100644
--- a/jim-package.c
+++ b/jim-package.c
@@ -47,7 +47,13 @@ static char *JimFindPackage(Jim_Interp *interp, Jim_Obj *prefixListObj, const ch
for (i = 0; i < prefixc; i++) {
Jim_Obj *prefixObjPtr = Jim_ListGetIndex(interp, prefixListObj, i);
- const char *prefix = Jim_String(prefixObjPtr);
+ const char *prefix;
+
+ if (Jim_GetObjTaint(prefixObjPtr)) {
+ /* This element is tainted, so ignore it */
+ continue;
+ }
+ prefix = Jim_String(prefixObjPtr);
/* Loadable modules are tried first */
#ifdef jim_ext_load
@@ -111,10 +117,8 @@ static int JimLoadPackage(Jim_Interp *interp, const char *name, int flags)
}
Jim_Free(path);
}
-
- return retCode;
}
- return JIM_ERR;
+ return retCode;
}
int Jim_PackageRequire(Jim_Interp *interp, const char *name, int flags)
diff --git a/jim-subcmd.c b/jim-subcmd.c
index 8a946ce..9ca89a7 100644
--- a/jim-subcmd.c
+++ b/jim-subcmd.c
@@ -230,14 +230,22 @@ int Jim_CallSubCmd(Jim_Interp *interp, const jim_subcmd_type * ct, int argc, Jim
int ret = JIM_ERR;
if (ct) {
- if (ct->flags & JIM_MODFLAG_FULLARGV) {
+ if ((ct->flags & JIM_MODFLAG_NOTAINT) && Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ ret = JIM_SUBCMD_TAINTED;
+ }
+ else if (ct->flags & JIM_MODFLAG_FULLARGV) {
ret = ct->function(interp, argc, argv);
}
else {
ret = ct->function(interp, argc - 2, argv + 2);
}
if (ret < 0) {
- Jim_SubCmdArgError(interp, ct, argv[0]);
+ if (ret == JIM_SUBCMD_TAINTED) {
+ Jim_SetTaintError(interp, 2, argv);
+ }
+ else {
+ Jim_SubCmdArgError(interp, ct, argv[0]);
+ }
ret = JIM_ERR;
}
}
diff --git a/jim-subcmd.h b/jim-subcmd.h
index a2fe548..a88a2f0 100644
--- a/jim-subcmd.h
+++ b/jim-subcmd.h
@@ -13,6 +13,10 @@ extern "C" {
#define JIM_MODFLAG_HIDDEN 0x0001 /* Don't show the subcommand in usage or commands */
#define JIM_MODFLAG_FULLARGV 0x0002 /* Subcmd proc gets called with full argv */
+#define JIM_MODFLAG_NOTAINT 0x0004 /* May not be called with tainted data */
+
+#define JIM_SUBCMD_BADARGS -1
+#define JIM_SUBCMD_TAINTED -2
/* Custom flags start at 0x0100 */
diff --git a/jim.c b/jim.c
index 1fd1c02..13ad575 100644
--- a/jim.c
+++ b/jim.c
@@ -2227,6 +2227,7 @@ Jim_Obj *Jim_NewObj(Jim_Interp *interp)
* kind of GC implemented should take care to avoid
* scanning objects with refCount == 0. */
objPtr->refCount = 0;
+ objPtr->taint = interp->taint;
/* All the other fields are left uninitialized to save time.
* The caller will probably want to set them to the right
* value anyway. */
@@ -2293,6 +2294,8 @@ Jim_Obj *Jim_DuplicateObj(Jim_Interp *interp, Jim_Obj *objPtr)
Jim_Obj *dupPtr;
dupPtr = Jim_NewObj(interp);
+ dupPtr->taint = objPtr->taint;
+
if (objPtr->bytes == NULL) {
/* Object does not have a valid string representation. */
dupPtr->bytes = NULL;
@@ -2573,6 +2576,7 @@ void Jim_AppendObj(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *appendObjPtr)
int len;
const char *str = Jim_GetString(appendObjPtr, &len);
Jim_AppendString(interp, objPtr, str, len);
+ objPtr->taint |= appendObjPtr->taint;
}
void Jim_AppendStrings(Jim_Interp *interp, Jim_Obj *objPtr, ...)
@@ -3744,6 +3748,7 @@ static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
ParseTokenList tokenlist;
Jim_Obj *fileNameObj;
int line;
+ int oldtaint;
/* Try to get information about filename / line number */
fileNameObj = Jim_GetSourceInfo(interp, objPtr, &line);
@@ -3762,6 +3767,11 @@ static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
ScriptAddToken(&tokenlist, scriptText + scriptTextLen, 0, JIM_TT_EOF, 0);
/* Create the "real" script tokens from the parsed tokens */
+
+ /* Set the correct taint on the objects in the script */
+ oldtaint = interp->taint;
+ interp->taint = objPtr->taint;
+
script = Jim_Alloc(sizeof(*script));
memset(script, 0, sizeof(*script));
script->inUse = 1;
@@ -3779,6 +3789,8 @@ static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
Jim_FreeIntRep(interp, objPtr);
Jim_SetIntRepPtr(objPtr, script);
objPtr->typePtr = &scriptObjType;
+
+ interp->taint = oldtaint;
}
/**
@@ -4590,7 +4602,7 @@ static int SetVariableFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
/* -------------------- Variables related functions ------------------------- */
static int JimDictSugarSet(Jim_Interp *interp, Jim_Obj *ObjPtr, Jim_Obj *valObjPtr);
-static Jim_Obj *JimDictSugarGet(Jim_Interp *interp, Jim_Obj *ObjPtr, int flags);
+static Jim_Obj *JimDictSugarGet(Jim_Interp *interp, Jim_Obj *objPtr, int flags);
static int JimSetNewVariable(Jim_HashTable *ht, Jim_Obj *nameObjPtr, Jim_VarVal *vv)
{
@@ -4981,6 +4993,7 @@ static void JimDictSugarParseVarKey(Jim_Interp *interp, Jim_Obj *objPtr,
JimPanic((p == NULL, "JimDictSugarParseVarKey() called for non-dict-sugar (%s)", str));
varObjPtr = Jim_NewStringObj(interp, str, p - str);
+ varObjPtr->taint = objPtr->taint;
p++;
keyLen = (str + len) - p;
@@ -4990,6 +5003,7 @@ static void JimDictSugarParseVarKey(Jim_Interp *interp, Jim_Obj *objPtr,
/* Create the objects with the variable name and key. */
keyObjPtr = Jim_NewStringObj(interp, p, keyLen);
+ keyObjPtr->taint = objPtr->taint;
Jim_IncrRefCount(varObjPtr);
Jim_IncrRefCount(keyObjPtr);
@@ -6288,6 +6302,7 @@ Jim_Obj *Jim_NewIntObj(Jim_Interp *interp, jim_wide wideValue)
objPtr = Jim_NewObj(interp);
objPtr->typePtr = &intObjType;
objPtr->bytes = NULL;
+ objPtr->taint = 0;
objPtr->internalRep.wideValue = wideValue;
return objPtr;
}
@@ -6436,6 +6451,7 @@ Jim_Obj *Jim_NewDoubleObj(Jim_Interp *interp, double doubleValue)
objPtr = Jim_NewObj(interp);
objPtr->typePtr = &doubleObjType;
objPtr->bytes = NULL;
+ objPtr->taint = 0;
objPtr->internalRep.doubleValue = doubleValue;
return objPtr;
}
@@ -6842,6 +6858,7 @@ static int SetListFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
continue;
elementPtr = JimParserGetTokenObj(interp, &parser);
Jim_SetSourceInfo(interp, elementPtr, fileNameObj, parser.tline);
+ elementPtr->taint = objPtr->taint;
ListAppendElement(objPtr, elementPtr);
}
}
@@ -6856,6 +6873,7 @@ Jim_Obj *Jim_NewListObj(Jim_Interp *interp, Jim_Obj *const *elements, int len)
objPtr = Jim_NewObj(interp);
objPtr->typePtr = &listObjType;
objPtr->bytes = NULL;
+ objPtr->taint = 0;
objPtr->internalRep.listValue.ele = NULL;
objPtr->internalRep.listValue.len = 0;
objPtr->internalRep.listValue.maxLen = 0;
@@ -7175,6 +7193,7 @@ static void ListInsertElements(Jim_Obj *listPtr, int idx, int elemc, Jim_Obj *co
memmove(point + elemc, point, (currentLen - idx) * sizeof(Jim_Obj *));
for (i = 0; i < elemc; ++i) {
point[i] = elemVec[i];
+ listPtr->taint |= point[i]->taint;
Jim_IncrRefCount(point[i]);
}
listPtr->internalRep.listValue.len += elemc;
@@ -7323,6 +7342,7 @@ static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx,
idx = listPtr->internalRep.listValue.len + idx;
Jim_DecrRefCount(interp, listPtr->internalRep.listValue.ele[idx]);
listPtr->internalRep.listValue.ele[idx] = newObjPtr;
+ listPtr->taint |= newObjPtr->taint;
Jim_IncrRefCount(newObjPtr);
return JIM_OK;
}
@@ -7861,6 +7881,7 @@ static int DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr,
dict->table[dict->len++] = valueObjPtr;
}
+ objPtr->taint |= keyObjPtr->taint | valueObjPtr->taint;
return JIM_OK;
}
}
@@ -8020,6 +8041,9 @@ int Jim_SetDictKeysVector(Jim_Interp *interp, Jim_Obj *varNamePtr,
goto err;
}
}
+ if (newObjPtr) {
+ varObjPtr->taint |= newObjPtr->taint;
+ }
break;
}
@@ -9795,6 +9819,9 @@ static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
exprText = Jim_GetString(objPtr, &exprTextLen);
+ int oldtaint = interp->taint;
+ interp->taint = objPtr->taint;
+
/* Initially tokenise the expression into tokenlist */
ScriptTokenListInit(&tokenlist);
@@ -9861,6 +9888,9 @@ static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
Jim_FreeIntRep(interp, objPtr);
Jim_SetIntRepPtr(objPtr, expr);
objPtr->typePtr = &exprObjType;
+
+ interp->taint = oldtaint;
+
return rc;
}
@@ -10919,6 +10949,7 @@ tailcall:
(retcode = JimTraceCallback(interp, "cmd", objc, objv)) == JIM_OK) {
/* Call it -- Make sure result is an empty object. */
Jim_SetEmptyResult(interp);
+ interp->taint = Jim_CalcTaint(objc, objv);
if (cmdPtr->isproc) {
retcode = JimCallProcedure(interp, cmdPtr, objc, objv);
}
@@ -11086,6 +11117,7 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
Jim_Obj *sintv[JIM_EVAL_SINTV_LEN];
Jim_Obj *objPtr;
char *s;
+ int taint = 0;
if (tokens <= JIM_EVAL_SINTV_LEN)
intv = sintv;
@@ -11123,6 +11155,7 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
}
return NULL;
}
+ taint |= intv[i]->taint;
Jim_IncrRefCount(intv[i]);
Jim_String(intv[i]);
totlen += intv[i]->length;
@@ -11138,6 +11171,7 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok
/* Concatenate every token in an unique
* object. */
objPtr = Jim_NewStringObjNoAlloc(interp, NULL, 0);
+ objPtr->taint = taint;
if (tokens == 4 && token[0].type == JIM_TT_ESC && token[1].type == JIM_TT_ESC
&& token[2].type == JIM_TT_VAR) {
@@ -11408,6 +11442,7 @@ int Jim_EvalObj(Jim_Interp *interp, Jim_Obj *scriptObjPtr)
if (retcode == JIM_OK && argc) {
/* Invoke the command */
retcode = JimInvokeCommand(interp, argc, argv);
+ interp->taint = 0;
/* Check for a signal after each command */
if (Jim_CheckSignal(interp)) {
retcode = JIM_SIGNAL;
@@ -11828,6 +11863,7 @@ static int SetSubstFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, int flags
struct JimParserCtx parser;
struct ScriptObj *script = Jim_Alloc(sizeof(*script));
ParseTokenList tokenlist;
+ int oldtaint;
/* Initially parse the subst into tokens (in tokenlist) */
ScriptTokenListInit(&tokenlist);
@@ -11843,6 +11879,9 @@ static int SetSubstFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, int flags
parser.tline);
}
+ oldtaint = interp->taint;
+ interp->taint = objPtr->taint;
+
/* Create the "real" subst/script tokens from the initial token list */
script->inUse = 1;
script->substFlags = flags;
@@ -11869,6 +11908,7 @@ static int SetSubstFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, int flags
Jim_FreeIntRep(interp, objPtr);
Jim_SetIntRepPtr(objPtr, script);
objPtr->typePtr = &scriptObjType;
+ interp->taint = oldtaint;
return JIM_OK;
}
@@ -11908,23 +11948,57 @@ int Jim_SubstObj(Jim_Interp *interp, Jim_Obj *substObjPtr, Jim_Obj **resObjPtrPt
/* -----------------------------------------------------------------------------
* Core commands utility functions
* ---------------------------------------------------------------------------*/
-void Jim_WrongNumArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv, const char *msg)
+static Jim_Obj *JimJoinCmdArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
Jim_Obj *objPtr;
Jim_Obj *listObjPtr;
- JimPanic((argc == 0, "Jim_WrongNumArgs() called with argc=0"));
-
listObjPtr = Jim_NewListObj(interp, argv, argc);
- if (msg && *msg) {
- Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, msg, -1));
- }
Jim_IncrRefCount(listObjPtr);
objPtr = Jim_ListJoin(interp, listObjPtr, " ", 1);
Jim_DecrRefCount(interp, listObjPtr);
+ Jim_IncrRefCount(objPtr);
+
+ return objPtr;
+}
+
+void Jim_WrongNumArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv, const char *msg)
+{
+ Jim_Obj *objPtr = JimJoinCmdArgs(interp, argc, argv);
+ if (*msg) {
+ Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s %s\"", objPtr, msg);
+ }
+ else {
+ Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s\"", objPtr);
+ }
+ Jim_DecrRefCount(interp, objPtr);
+}
- Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s\"", objPtr);
+/**
+ * Calculates the taint of the given objects (skipping NULL entries).
+ */
+int Jim_CalcTaint(int argc, Jim_Obj *const *argv)
+{
+ int taint = 0;
+#ifdef JIM_TAINT
+ int i;
+ for (i = 0; i < argc; i++) {
+ if (argv[i]) {
+ taint |= argv[i]->taint;
+ }
+ }
+#endif
+ return taint;
+}
+
+void Jim_SetTaintError(Jim_Interp *interp, int cmdargs, Jim_Obj *const *argv)
+{
+#ifdef JIM_TAINT
+ Jim_Obj *objPtr = JimJoinCmdArgs(interp, cmdargs, argv);
+ Jim_SetResultFormatted(interp, "%#s: tainted data", objPtr);
+ Jim_DecrRefCount(interp, objPtr);
+#endif
}
/**
@@ -13675,7 +13749,7 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
JIM_DEF_SUBCMD("exprlen", "expression", 1, 1),
JIM_DEF_SUBCMD("invstr", "object", 1, 1),
JIM_DEF_SUBCMD("objcount", NULL, 0, 0),
- JIM_DEF_SUBCMD("objects", NULL, 0, 0),
+ JIM_DEF_SUBCMD("objects", "?-taint?", 0, 1),
JIM_DEF_SUBCMD("refcount", "object", 1, 1),
JIM_DEF_SUBCMD("scriptlen", "script", 1, 1),
JIM_DEF_SUBCMD("show", "object", 1, 1),
@@ -13721,6 +13795,17 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
case OPT_OBJECTS:{
Jim_Obj *objPtr, *listObjPtr, *subListObjPtr;
+ int tainted;
+
+ if (argc == 3) {
+ if (Jim_CompareStringImmediate(interp, argv[2], "-taint")) {
+ tainted = 1;
+ }
+ else {
+ Jim_SubCmdArgError(interp, ct, argv[0]);
+ return JIM_ERR;
+ }
+ }
/* Count the number of live objects. */
objPtr = interp->liveList;
@@ -13729,14 +13814,24 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
char buf[128];
const char *type = objPtr->typePtr ? objPtr->typePtr->name : "";
- subListObjPtr = Jim_NewListObj(interp, NULL, 0);
- sprintf(buf, "%p", objPtr);
- Jim_ListAppendElement(interp, subListObjPtr, Jim_NewStringObj(interp, buf, -1));
- Jim_ListAppendElement(interp, subListObjPtr, Jim_NewStringObj(interp, type, -1));
- Jim_ListAppendElement(interp, subListObjPtr, Jim_NewIntObj(interp, objPtr->refCount));
- Jim_ListAppendElement(interp, subListObjPtr, objPtr);
- Jim_ListAppendElement(interp, listObjPtr, subListObjPtr);
- objPtr = objPtr->nextObjPtr;
+ /* Return a list of the objects */
+ listObjPtr = Jim_NewListObj(interp, NULL, 0);
+ for (objPtr = interp->liveList; objPtr; objPtr = objPtr->nextObjPtr) {
+ char buf[128];
+ const char *type = objPtr->typePtr ? objPtr->typePtr->name : "";
+
+ if (objPtr == listObjPtr || (tainted && !objPtr->taint)) {
+ continue;
+ }
+
+ subListObjPtr = Jim_NewListObj(interp, NULL, 0);
+ sprintf(buf, "%p", objPtr);
+ Jim_ListAppendElement(interp, subListObjPtr, Jim_NewStringObj(interp, buf, -1));
+ Jim_ListAppendElement(interp, subListObjPtr, Jim_NewStringObj(interp, type, -1));
+ Jim_ListAppendElement(interp, subListObjPtr, Jim_NewIntObj(interp, objPtr->refCount));
+ Jim_ListAppendElement(interp, subListObjPtr, objPtr);
+ Jim_ListAppendElement(interp, listObjPtr, subListObjPtr);
+ }
}
Jim_SetResult(interp, listObjPtr);
return JIM_OK;
@@ -13765,9 +13860,9 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
charlen = len;
#endif
char buf[256];
- snprintf(buf, sizeof(buf), "refcount: %d, type: %s\n"
+ snprintf(buf, sizeof(buf), "refcount: %d, taint: %d, type: %s\n"
"chars (%d):",
- argv[2]->refCount, JimObjTypeName(argv[2]), charlen);
+ argv[2]->refCount, argv[2]->taint, JimObjTypeName(argv[2]), charlen);
Jim_SetResultFormatted(interp, "%s <<%s>>\n", buf, s);
snprintf(buf, sizeof(buf), "bytes (%d):", len);
Jim_AppendString(interp, Jim_GetResult(interp), buf, -1);
@@ -14256,6 +14351,61 @@ static int Jim_ApplyCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
}
}
+#ifdef JIM_TAINT
+static int JimTaintVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int taint)
+{
+ Jim_Obj *valueObjPtr = Jim_GetVariable(interp, nameObjPtr, JIM_ERRMSG | JIM_UNSHARED);
+
+ if (valueObjPtr == NULL) {
+ return JIM_ERR;
+ }
+
+ if (Jim_IsShared(valueObjPtr)) {
+ valueObjPtr = Jim_DuplicateObj(interp, valueObjPtr);
+ Jim_SetVariable(interp, nameObjPtr, valueObjPtr);
+ }
+
+ /* If this is an array element or a list, need to invalidate the string rep to
+ * force recalc. When it is regenerated, the whole string will be tainted if any component is.
+ */
+ if (taint && nameObjPtr->typePtr == &dictSubstObjType) {
+ /* Tainting an array element taints the array too */
+ Jim_Obj *objPtr = Jim_GetVariable(interp, nameObjPtr->internalRep.dictSubstValue.varNameObjPtr, JIM_NONE);
+ if (objPtr) {
+ SetStringFromAny(interp, valueObjPtr);
+ valueObjPtr->taint = taint;
+ objPtr->taint |= taint;
+ }
+ }
+
+ /* Manually tainting destroys any non-string rep */
+ SetStringFromAny(interp, valueObjPtr);
+ valueObjPtr->taint = taint;
+
+ //printf("taint of %s is %d\n", valueObjPtr->bytes, valueObjPtr->taint);
+ return JIM_OK;
+}
+
+/* [taint] */
+static int Jim_TaintCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ if (argc != 2) {
+ Jim_WrongNumArgs(interp, 1, argv, "varname");
+ return JIM_ERR;
+ }
+ return JimTaintVariable(interp, argv[1], 1);
+}
+
+/* [untaint] */
+static int Jim_UntaintCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ if (argc != 2) {
+ Jim_WrongNumArgs(interp, 1, argv, "varname");
+ return JIM_ERR;
+ }
+ return JimTaintVariable(interp, argv[1], 0);
+}
+#endif
/* [concat] */
static int Jim_ConcatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
@@ -15661,6 +15811,7 @@ static int Jim_InfoCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg
INFO_SOURCE,
INFO_STACKTRACE,
INFO_STATICS,
+ INFO_TAINTED,
INFO_VARS,
INFO_VERSION,
INFO_COUNT
@@ -15687,6 +15838,7 @@ static int Jim_InfoCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg
JIM_DEF_SUBCMD("source", "source ?filename line?", 1, 3),
JIM_DEF_SUBCMD("stacktrace", NULL, 0, 0),
JIM_DEF_SUBCMD("statics", "procname", 1, 1),
+ JIM_DEF_SUBCMD("tainted", "str", 1, 1),
JIM_DEF_SUBCMD("vars", "?pattern?", 0, 1),
JIM_DEF_SUBCMD("version", NULL, 0, 0),
{ NULL }
@@ -15732,6 +15884,13 @@ static int Jim_InfoCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg
return JIM_OK;
}
+ case INFO_TAINTED:
+ if (argc != 3) {
+ Jim_WrongNumArgs(interp, 2, argv, "value");
+ return JIM_ERR;
+ }
+ Jim_SetResultBool(interp, argv[2]->taint != 0);
+ return JIM_OK;
case INFO_CHANNELS:
mode++; /* JIM_CMDLIST_CHANNELS */
#ifndef jim_ext_aio
@@ -16334,14 +16493,18 @@ static int Jim_SourceCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a
{
int retval;
+ if (Jim_CheckTaint(interp, JIM_TAINT_ANY)) {
+ Jim_SetTaintError(interp, 1, argv);
+ return JIM_ERR;
+ }
+
if (argc != 2) {
Jim_WrongNumArgs(interp, 1, argv, "fileName");
return JIM_ERR;
}
retval = Jim_EvalFile(interp, Jim_String(argv[1]));
- if (retval == JIM_RETURN)
- return JIM_OK;
- return retval;
+
+ return retval == JIM_RETURN ? JIM_OK : retval;
}
/* [lreverse] */
@@ -16540,6 +16703,10 @@ static const struct {
{"upcall", Jim_UpcallCoreCommand},
{"apply", Jim_ApplyCoreCommand},
{"stacktrace", Jim_StacktraceCoreCommand},
+#ifdef JIM_TAINT
+ {"taint", Jim_TaintCoreCommand},
+ {"untaint", Jim_UntaintCoreCommand},
+#endif
{NULL, NULL},
};
diff --git a/jim.h b/jim.h
index 8a0ae1e..d1ae690 100644
--- a/jim.h
+++ b/jim.h
@@ -150,7 +150,6 @@ extern "C" {
#define JIM_NONE 0 /* no flags set */
#define JIM_ERRMSG 1 /* set an error message in the interpreter. */
-#define JIM_ENUM_ABBREV 2 /* Jim_GetEnum() - Allow unambiguous abbreviation */
#define JIM_UNSHARED 4 /* Jim_GetVariable() - return unshared object */
#define JIM_MUSTEXIST 8 /* Jim_SetDictKeysVector() - fail if non-existent */
#define JIM_NORESULT 16 /* Jim_SetDictKeysVector() - don't store the result in the interp result */
@@ -161,6 +160,14 @@ extern "C" {
#define JIM_SUBST_NOESC 4 /* don't perform escapes substitutions */
#define JIM_SUBST_FLAG 128 /* flag to indicate that this is a real substitution object */
+#define JIM_TAINT_STD 1 /* The "normal" type of taint. Allows for multiple
+ * types of taint in the future
+ */
+#define JIM_TAINT_ANY ~0 /* Any type of taint at all */
+
+/* Flags for Jim_GetEnum() */
+#define JIM_ENUM_ABBREV 2 /* Allow unambiguous abbreviation */
+
/* Flags used by API calls getting a 'nocase' argument. */
#define JIM_CASESENS 0 /* case sensitive */
#define JIM_NOCASE 1 /* no case */
@@ -286,6 +293,7 @@ typedef struct Jim_Obj {
const struct Jim_ObjType *typePtr; /* object type. */
int refCount; /* reference count */
int length; /* number of bytes in 'bytes', not including the null term. */
+ unsigned taint; /* If this object is tainted */
/* Internal representation union */
union {
/* integer number type */
@@ -599,6 +607,7 @@ typedef struct Jim_Interp {
Jim_PrngState *prngState; /* per interpreter Random Number Gen. state. */
struct Jim_HashTable packages; /* Provided packages hash table */
Jim_Stack *loadHandles; /* handles of loaded modules [load] */
+ unsigned taint; /* Newly created objects get this taint */
} Jim_Interp;
/* Currently provided as macro that performs the increment.
@@ -983,6 +992,22 @@ JIM_EXPORT int Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command);
JIM_EXPORT int Jim_IsDict(Jim_Obj *objPtr);
JIM_EXPORT int Jim_IsList(Jim_Obj *objPtr);
+/* taint */
+JIM_EXPORT void Jim_SetTaintError(Jim_Interp *interp, int cmdargs, Jim_Obj *const *argv);
+JIM_EXPORT int Jim_CalcTaint(int argc, Jim_Obj *const *argv);
+
+#ifdef JIM_TAINT
+#define Jim_CheckTaint(i, t) ((i)->taint & (t))
+#define Jim_TaintObj(o,t) (o)->taint |= (t)
+#define Jim_UntaintObj(o) (o)->taint = 0
+#define Jim_GetObjTaint(o) (o)->taint
+#else
+#define Jim_CheckTaint(i, t) 0
+#define Jim_TaintObj(o,t)
+#define Jim_UntaintObj(o)
+#define Jim_GetObjTaint(o) 0
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/tests/debug.test b/tests/debug.test
index a86d472..111cdeb 100644
--- a/tests/debug.test
+++ b/tests/debug.test
@@ -40,7 +40,7 @@ test debug-3.1 {debug objects} -body {
# does not currently check for too many args
test debug-3.2 {debug objects too many args} -body {
debug objects a b c
-} -returnCodes error -result {wrong # args: should be "debug objects"}
+} -returnCodes error -result {wrong # args: should be "debug objects ?-taint?"}
test debug-4.1 {debug invstr too few args} -body {
debug invstr
@@ -100,7 +100,7 @@ test debug-8.1 {debug show} -body {
set x hello
lappend x there
debug show $x
-} -result {refcount: 2, type: list
+} -result {refcount: 2, taint: 0, type: list
chars (11): <<hello there>>
bytes (11): 68 65 6c 6c 6f 20 74 68 65 72 65}
diff --git a/tests/socket.test b/tests/socket.test
index cc7d3d6..acb5347 100644
--- a/tests/socket.test
+++ b/tests/socket.test
@@ -120,6 +120,11 @@ test socket-1.7 {socketpair} -body {
lassign [socket pair] s1 s2
$s1 buffering line
$s2 buffering line
+ # We trust the data received on these sockets
+ if {[exists -command taint]} {
+ $s1 taint source 0
+ $s2 taint source 0
+ }
stdout flush
if {[os.fork] == 0} {
$s1 close
@@ -345,6 +350,10 @@ if {[os.fork] == 0} {
# read everything available (non-blocking read)
set buf [$c read]
if {[string length $buf]} {
+ # It is safe to send this back to where it came from
+ if {[exists -command untaint]} {
+ untaint buf
+ }
$c puts -nonewline $buf
$c flush
}
diff --git a/tests/ssl.test b/tests/ssl.test
index d147c92..7c69358 100644
--- a/tests/ssl.test
+++ b/tests/ssl.test
@@ -22,6 +22,10 @@ if {[os.fork] == 0} {
$c readable {
# read everything available and echo it back
set buf [$c read]
+ # We don't mind sending tainted data back to it's source
+ if {[exists -command taint]} {
+ untaint buf
+ }
$c puts -nonewline $buf
$c flush
if {[$c eof]} {
diff --git a/tests/taint.test b/tests/taint.test
new file mode 100644
index 0000000..6727839
--- /dev/null
+++ b/tests/taint.test
@@ -0,0 +1,204 @@
+source [file dirname [info script]]/testing.tcl
+
+needs cmd taint
+
+# create a tainted var
+set t tainted
+taint t
+
+test taint-1.1 {taint simple var} {
+ info tainted $t
+} 1
+
+test taint-1.2 {set taint, simple var} {
+ set x $t
+ info tainted $x
+} 1
+
+test taint-1.3 {untaint ref counting simple var} {
+ untaint x
+ list [info tainted $x] [info tainted $t]
+} {0 1}
+
+# Tainting an array element taints the array/dict, but
+# not each element
+test taint-1.4 {taint array var} {
+ set a {1 one 2 two}
+ taint a(2)
+ list [info tainted $a(1)] [info tainted $a(2)] [info tainted $a]
+} {0 1 1}
+
+# Adding a tainted value to an array taints the array/dict, but
+# not each element
+test taint-1.5 {tainted value taints dict} {
+ unset -nocomplain a
+ array set a {1 one 2 two}
+ set a(3) $t
+ list [info tainted $a(1)] [info tainted $a(3)] [info tainted $a]
+} {0 1 1}
+
+# lappend taints the list, but not each element
+test taint-1.6 {lappend with taint} {
+ set a {1 2}
+ lappend a $t
+ list [info tainted $a] [lmap p $a {info tainted $p}]
+} {1 {0 0 1}}
+
+# lset taints the list, but not each element
+test taint-1.7 {lset with taint} {
+ set a [list a b c d]
+ lset a 1 $t
+ list [info tainted $a] [lmap p $a {info tainted $p}]
+} {1 {0 1 0 0}}
+
+# append taints the string
+test taint-1.8 {append with taint} {
+ set a abc
+ append a $t
+ info tainted $a
+} 1
+
+test taint-1.9 {taint entire list} {
+ set a [list 1 2 3]
+ taint a
+ list [info tainted $a] [lmap p $a {info tainted $p}]
+} {1 {1 1 1}}
+
+test taint-1.10 {taint entire dict} {
+ set a [dict create a 1 b 2 c 3]
+ taint a
+ list [info tainted $a] [info tainted [dict get $a b]]
+} {1 1}
+
+
+test taint-1.11 {interpolation with taint} {
+ set x "x$t"
+ info tainted $x
+} 1
+
+test taint-1.12 {lrange with taint} {
+ set a [list 1 2 3 $t 5 6]
+ info tainted [lrange $a 0 1]
+} 0
+
+test taint-1.13 {lrange with taint} {
+ set a [list 1 2 3 $t 5 6]
+ info tainted [lrange $a 2 4]
+} 1
+
+test taint-1.14 {lindex with taint} {
+ set a [list 1 2 3 $t 5 6]
+ info tainted [lindex $a 1]
+} 0
+
+test taint-1.15 {lassign with taint} {
+ set a [list 1 $t 3]
+ lassign $a x y z
+ list [info tainted $x] [info tainted $y] [info tainted $z]
+} {0 1 0}
+
+test taint-1.16 {lreverse with taint} {
+ set a [lreverse [list 1 2 $t]]
+ list [info tainted $a] [lmap p $a {info tainted $p}]
+} {1 {1 0 0}}
+
+test taint-1.17 {lsort with taint} {
+ set a [lsort [list zzz aaa $t bbb ppp]]
+ list [info tainted $a] [lmap p $a {info tainted $p}]
+} {1 {0 0 0 1 0}}
+
+test taint-1.18 {lreplace with taint} {
+ set a {a b c}
+ set b [lreplace $a 1 1 $t]
+ list [info tainted $b] [lmap p $b {info tainted $p}]
+} {1 {0 1 0}}
+
+test taint-1.19 {dict with taint} {
+ set a [dict create a 1 b 2 c $t d 4]
+ info tainted $a
+} 1
+
+test taint-1.20 {dict with taint} {
+ set a [dict create a 1 b 2 c $t d 4]
+ info tainted [dict get $a b]
+} 0
+
+test taint-1.21 {dict with taint} {
+ set a [dict create a 1 b 2 c $t d 4]
+ info tainted [dict get $a c]
+} 1
+
+test taint-1.22 {dict with taint} {
+ dict set a $t e
+ set result {}
+ foreach i [lsort [dict keys $a]] {
+ set v [dict get $a $i]
+ lappend result [list $i $v [info tainted $i] [info tainted $v]]
+ }
+ set result
+} {{a 1 0 0} {b 2 0 0} {c tainted 0 1} {d 4 0 0} {tainted e 1 0}}
+
+test taint-1.23 {nested dict with taint} {
+ set a [dict create]
+ dict set a 1 A 1-A
+ dict set a 2 A 2-A
+ dict set a 1 T $t
+ info tainted $a
+} 1
+
+test taint-2.1 {exec with tainted data} -body {
+ exec $t
+} -returnCodes error -result {exec: tainted data}
+
+test taint-2.2 {eval with tainted data - allowed} {
+ eval "set a $t"
+} tainted
+
+test taint-2.3 {eval with braced tainted data - allowed} {
+ eval {set a $t}
+} tainted
+
+test taint-2.4 {eval exec with tainted data} -body {
+ eval {exec $t}
+} -returnCodes error -result {exec: tainted data}
+
+test taint-2.5 {open with tainted data} -body {
+ open "|$t"
+} -returnCodes error -result {open: tainted data}
+
+test taint-2.6 {file delete with tainted data} -body {
+ file delete $t
+} -returnCodes error -result {file delete: tainted data}
+
+test taint-3.1 {filehandle not taint source by default} {
+ set f [open [info script]]
+ gets $f buf
+ info tainted $buf
+} 0
+
+test taint-3.2 {set taint source on filehandle} {
+ $f taint source 1
+ gets $f buf
+ info tainted $buf
+} 1
+
+test taint-3.3 {filehandle not taint sink by default} -body {
+ set g [open out.tmp w]
+ puts $g $t
+} -result {}
+
+test taint-3.4 {set taint sink on filehandle} -body {
+ $g taint sink 1
+ puts $g $t
+} -returnCodes error -result "puts: tainted data"
+
+test taint-3.5 {copyto taint source to sink} -body {
+ $f copyto $g
+} -returnCodes error -result {copying tainted source}
+
+$f close
+$g close
+
+file delete out.tmp
+
+testreport