diff options
author | Steve Bennett <steveb@workware.net.au> | 2011-05-18 08:24:47 +1000 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2025-07-16 09:34:08 +1000 |
commit | f72a03487ec28328678d48472026877e7d66bc29 (patch) | |
tree | 97a8a5d3f0dd30100f8d7ca958fb03abb5f56a74 | |
parent | ee3d7dc3283b6aa37332a69354f5d98eaea055dd (diff) | |
download | jimtcl-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.taint | 143 | ||||
-rw-r--r-- | auto.def | 8 | ||||
-rw-r--r-- | examples/udp.client | 5 | ||||
-rw-r--r-- | examples/udp.server | 6 | ||||
-rw-r--r-- | jim-aio.c | 171 | ||||
-rw-r--r-- | jim-exec.c | 5 | ||||
-rw-r--r-- | jim-file.c | 9 | ||||
-rw-r--r-- | jim-load.c | 5 | ||||
-rw-r--r-- | jim-package.c | 12 | ||||
-rw-r--r-- | jim-subcmd.c | 12 | ||||
-rw-r--r-- | jim-subcmd.h | 4 | ||||
-rw-r--r-- | jim.c | 211 | ||||
-rw-r--r-- | jim.h | 27 | ||||
-rw-r--r-- | tests/debug.test | 4 | ||||
-rw-r--r-- | tests/socket.test | 9 | ||||
-rw-r--r-- | tests/ssl.test | 4 | ||||
-rw-r--r-- | tests/taint.test | 204 |
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. @@ -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 @@ -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; } @@ -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. @@ -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; @@ -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 */ @@ -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}, }; @@ -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 |