Commit 8c7ad0bd authored by Duncan Coutts's avatar Duncan Coutts

Change what +RTS options are available by default

Ticket #3910 originally pointed out that the RTS options are a potential
security problem. For example the -t -s or -S flags can be used to
overwrite files. This would be bad in the context of CGI scripts or
setuid binaries. So we introduced a system where +RTS processing is more
or less disabled unless you pass the -rtsopts flag at link time.

This scheme is safe enough but it also really annoies users. They have
to use -rtsopts in many circumstances: with -threaded to use -N, with
-eventlog to use -l, with -prof to use any of the profiling flags. Many
users just set -rtsopts globally or in project .cabal files. Apart from
annoying users it reduces security because it means that deployed
binaries will have all RTS options enabled rather than just profiling
ones.

This patch relaxes the set of RTS options that are available in the
default -rtsopts=some case. For "deployment" ways like vanilla and
-threaded we remain quite conservative. Only --info -? --help are
allowed for vanilla. For -threaded, -N and -N<x> are allowed with a
check that x <= num cpus.

For "developer" ways like -debug, -eventlog, -prof, we allow all the
options that are special to that way. Some of these allow writing files,
but the file written is not directly under the control of the attacker.
For the setuid case (where the attacker would have control over binary
name, current dir, local symlinks etc) we check if the process is
running setuid/setgid and refuse all RTS option processing. Users would
need to use -rtsopts=all in this case.

We are making the assumption that developers will not deploy binaries
built in the -debug, -eventlog, -prof ways. And even if they do, the
damage should be limited to DOS, information disclosure and writing
files like <progname>.eventlog, not arbitrary files.
parent 43a92710
......@@ -21,6 +21,14 @@
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
// Flag Structure
RTS_FLAGS RtsFlags;
......@@ -526,13 +534,54 @@ void setupRtsFlags (int *argc, char *argv[])
* procRtsOpts: Process rts_argv between rts_argc0 and rts_argc.
* -------------------------------------------------------------------------- */
static void checkSuid(RtsOptsEnabledEnum enabled)
{
#if defined(HAVE_UNISTD_H) && defined(HAVE_SYS_TYPES_H)
if (enabled == RtsOptsSafeOnly) {
/* This doesn't cover linux/posix capabilities like CAP_DAC_OVERRIDE,
we'd have to link with -lcap for that. */
if ((getuid() != geteuid()) || (getgid() != getegid())) {
errorBelch("RTS options are disabled for setuid binaries. Link with -rtsopts to enable them.");
stg_exit(EXIT_FAILURE);
}
}
#endif
}
static void checkUnsafe(RtsOptsEnabledEnum enabled)
{
if (enabled == RtsOptsSafeOnly) {
errorBelch("Most RTS options are disabled. Link with -rtsopts to enable them.");
stg_exit(EXIT_FAILURE);
}
}
static void procRtsOpts (int rts_argc0, RtsOptsEnabledEnum enabled)
{
rtsBool error = rtsFalse;
int arg;
if (!(rts_argc0 < rts_argc)) return;
if (enabled == RtsOptsNone) {
errorBelch("RTS options are disabled. Link with -rtsopts to enable them.");
stg_exit(EXIT_FAILURE);
}
checkSuid(rtsOptsEnabled);
// Process RTS (rts_argv) part: mainly to determine statsfile
for (arg = rts_argc0; arg < rts_argc; arg++) {
/* We handle RtsOptsSafeOnly mode by declaring each option as
either OPTION_SAFE or OPTION_UNSAFE. To make sure we cover
every branch we use an option_checked flag which is reset
at the start each iteration and checked at the end. */
rtsBool option_checked = rtsFalse;
#define OPTION_SAFE option_checked = rtsTrue;
#define OPTION_UNSAFE checkUnsafe(enabled); option_checked = rtsTrue;
if (rts_argv[arg][0] != '-') {
fflush(stdout);
errorBelch("unexpected RTS argument: %s", rts_argv[arg]);
......@@ -540,27 +589,6 @@ static void procRtsOpts (int rts_argc0, RtsOptsEnabledEnum enabled)
} else {
if (enabled == RtsOptsNone) {
errorBelch("RTS options are disabled. Link with -rtsopts to enable them.");
stg_exit(EXIT_FAILURE);
}
switch(rts_argv[arg][1]) {
case '-':
if (strequal("info", &rts_argv[arg][2])) {
printRtsInfo();
stg_exit(0);
}
break;
default:
break;
}
if (enabled == RtsOptsSafeOnly) {
errorBelch("Most RTS options are disabled. Link with -rtsopts to enable them.");
stg_exit(EXIT_FAILURE);
}
switch(rts_argv[arg][1]) {
/* process: general args, then PROFILING-only ones, then
......@@ -612,6 +640,7 @@ error = rtsTrue;
/* =========== GENERAL ========================== */
case '?':
OPTION_SAFE;
error = rtsTrue;
break;
......@@ -621,27 +650,33 @@ error = rtsTrue;
case '-':
if (strequal("install-signal-handlers=yes",
&rts_argv[arg][2])) {
OPTION_UNSAFE;
RtsFlags.MiscFlags.install_signal_handlers = rtsTrue;
}
else if (strequal("install-signal-handlers=no",
&rts_argv[arg][2])) {
OPTION_UNSAFE;
RtsFlags.MiscFlags.install_signal_handlers = rtsFalse;
}
else if (strequal("machine-readable",
&rts_argv[arg][2])) {
OPTION_UNSAFE;
RtsFlags.MiscFlags.machineReadable = rtsTrue;
}
else if (strequal("info",
&rts_argv[arg][2])) {
OPTION_SAFE;
printRtsInfo();
stg_exit(0);
}
else {
OPTION_SAFE;
errorBelch("unknown RTS option: %s",rts_argv[arg]);
error = rtsTrue;
}
break;
case 'A':
OPTION_UNSAFE;
RtsFlags.GcFlags.minAllocAreaSize
= decodeSize(rts_argv[arg], 2, BLOCK_SIZE, HS_INT_MAX)
/ BLOCK_SIZE;
......@@ -649,6 +684,7 @@ error = rtsTrue;
#ifdef USE_PAPI
case 'a':
OPTION_UNSAFE;
switch(rts_argv[arg][2]) {
case '1':
RtsFlags.PapiFlags.eventType = PAPI_FLAG_CACHE_L1;
......@@ -686,10 +722,12 @@ error = rtsTrue;
#endif
case 'B':
OPTION_UNSAFE;
RtsFlags.GcFlags.ringBell = rtsTrue;
break;
case 'c':
OPTION_UNSAFE;
if (rts_argv[arg][2] != '\0') {
RtsFlags.GcFlags.compactThreshold =
atof(rts_argv[arg]+2);
......@@ -699,10 +737,12 @@ error = rtsTrue;
break;
case 'w':
OPTION_UNSAFE;
RtsFlags.GcFlags.sweep = rtsTrue;
break;
case 'F':
OPTION_UNSAFE;
RtsFlags.GcFlags.oldGenFactor = atof(rts_argv[arg]+2);
if (RtsFlags.GcFlags.oldGenFactor < 0)
......@@ -710,6 +750,7 @@ error = rtsTrue;
break;
case 'D':
OPTION_SAFE;
DEBUG_BUILD_ONLY(
{
char *c;
......@@ -772,11 +813,13 @@ error = rtsTrue;
break;
case 'K':
OPTION_UNSAFE;
RtsFlags.GcFlags.maxStkSize =
decodeSize(rts_argv[arg], 2, sizeof(W_), HS_WORD_MAX) / sizeof(W_);
break;
case 'k':
OPTION_UNSAFE;
switch(rts_argv[arg][2]) {
case 'c':
RtsFlags.GcFlags.stkChunkSize =
......@@ -798,12 +841,14 @@ error = rtsTrue;
break;
case 'M':
OPTION_UNSAFE;
RtsFlags.GcFlags.maxHeapSize =
decodeSize(rts_argv[arg], 2, BLOCK_SIZE, HS_WORD_MAX) / BLOCK_SIZE;
/* user give size in *bytes* but "maxHeapSize" is in *blocks* */
break;
case 'm':
OPTION_UNSAFE;
RtsFlags.GcFlags.pcFreeHeap = atof(rts_argv[arg]+2);
if (RtsFlags.GcFlags.pcFreeHeap < 0 ||
......@@ -812,11 +857,13 @@ error = rtsTrue;
break;
case 'G':
OPTION_UNSAFE;
RtsFlags.GcFlags.generations =
decodeSize(rts_argv[arg], 2, 1, HS_INT_MAX);
break;
case 'H':
OPTION_UNSAFE;
if (rts_argv[arg][2] == '\0') {
RtsFlags.GcFlags.heapSizeSuggestionAuto = rtsTrue;
} else {
......@@ -827,11 +874,13 @@ error = rtsTrue;
#ifdef RTS_GTK_FRONTPANEL
case 'f':
OPTION_UNSAFE;
RtsFlags.GcFlags.frontpanel = rtsTrue;
break;
#endif
case 'I': /* idle GC delay */
OPTION_UNSAFE;
if (rts_argv[arg][2] == '\0') {
/* use default */
} else {
......@@ -844,18 +893,22 @@ error = rtsTrue;
break;
case 'T':
OPTION_UNSAFE;
RtsFlags.GcFlags.giveStats = COLLECT_GC_STATS;
break; /* Don't initialize statistics file. */
case 'S':
OPTION_UNSAFE;
RtsFlags.GcFlags.giveStats = VERBOSE_GC_STATS;
goto stats;
case 's':
OPTION_UNSAFE;
RtsFlags.GcFlags.giveStats = SUMMARY_GC_STATS;
goto stats;
case 't':
OPTION_UNSAFE;
RtsFlags.GcFlags.giveStats = ONELINE_GC_STATS;
goto stats;
......@@ -869,6 +922,7 @@ error = rtsTrue;
break;
case 'Z':
OPTION_UNSAFE;
RtsFlags.GcFlags.squeezeUpdFrames = rtsFalse;
break;
......@@ -876,6 +930,7 @@ error = rtsTrue;
case 'P': /* detailed cost centre profiling (time/alloc) */
case 'p': /* cost centre profiling (time/alloc) */
OPTION_SAFE;
PROFILING_BUILD_ONLY(
switch (rts_argv[arg][2]) {
case 'x':
......@@ -897,10 +952,12 @@ error = rtsTrue;
) break;
case 'R':
OPTION_SAFE;
PROFILING_BUILD_ONLY(
RtsFlags.ProfFlags.maxRetainerSetSize = atof(rts_argv[arg]+2);
) break;
case 'L':
OPTION_SAFE;
PROFILING_BUILD_ONLY(
RtsFlags.ProfFlags.ccsLength = atof(rts_argv[arg]+2);
if(RtsFlags.ProfFlags.ccsLength <= 0) {
......@@ -909,6 +966,7 @@ error = rtsTrue;
) break;
case 'h': /* serial heap profile */
#if !defined(PROFILING)
OPTION_UNSAFE;
switch (rts_argv[arg][2]) {
case '\0':
case 'T':
......@@ -919,6 +977,7 @@ error = rtsTrue;
error = rtsTrue;
}
#else
OPTION_SAFE;
PROFILING_BUILD_ONLY(
switch (rts_argv[arg][2]) {
case '\0':
......@@ -1027,6 +1086,7 @@ error = rtsTrue;
break;
case 'i': /* heap sample interval */
OPTION_UNSAFE;
if (rts_argv[arg][2] == '\0') {
/* use default */
} else {
......@@ -1040,6 +1100,7 @@ error = rtsTrue;
/* =========== CONCURRENT ========================= */
case 'C': /* context switch interval */
OPTION_UNSAFE;
if (rts_argv[arg][2] == '\0')
RtsFlags.ConcFlags.ctxtSwitchTime = 0;
else {
......@@ -1052,6 +1113,7 @@ error = rtsTrue;
break;
case 'V': /* master tick interval */
OPTION_UNSAFE;
if (rts_argv[arg][2] == '\0') {
// turns off ticks completely
RtsFlags.MiscFlags.tickInterval = 0;
......@@ -1066,6 +1128,7 @@ error = rtsTrue;
#if !defined(NOSMP)
case 'N':
OPTION_SAFE;
THREADED_BUILD_ONLY(
if (rts_argv[arg][2] == '\0') {
#if defined(PROFILING)
......@@ -1075,11 +1138,17 @@ error = rtsTrue;
#endif
} else {
int nNodes;
OPTION_SAFE; /* but see extra checks below... */
nNodes = strtol(rts_argv[arg]+2, (char **) NULL, 10);
if (nNodes <= 0) {
errorBelch("bad value for -N");
error = rtsTrue;
}
if (enabled == RtsOptsSafeOnly &&
nNodes > (int)getNumberOfProcessors()) {
errorBelch("Using large values for -N is not allowed by default. Link with -rtsopts to allow full control.");
stg_exit(EXIT_FAILURE);
}
#if defined(PROFILING)
if (nNodes > 1) {
errorBelch("bad option %s: only -N1 is supported with profiling", rts_argv[arg]);
......@@ -1091,6 +1160,7 @@ error = rtsTrue;
) break;
case 'g':
OPTION_UNSAFE;
THREADED_BUILD_ONLY(
switch (rts_argv[arg][2]) {
case '1':
......@@ -1105,6 +1175,7 @@ error = rtsTrue;
) break;
case 'q':
OPTION_UNSAFE;
THREADED_BUILD_ONLY(
switch (rts_argv[arg][2]) {
case '\0':
......@@ -1148,6 +1219,7 @@ error = rtsTrue;
#endif
/* =========== PARALLEL =========================== */
case 'e':
OPTION_UNSAFE;
THREADED_BUILD_ONLY(
if (rts_argv[arg][2] != '\0') {
RtsFlags.ParFlags.maxLocalSparks
......@@ -1162,6 +1234,7 @@ error = rtsTrue;
/* =========== TICKY ============================== */
case 'r': /* Basic profiling stats */
OPTION_SAFE;
TICKY_BUILD_ONLY(
RtsFlags.TickyFlags.showTickyStats = rtsTrue;
......@@ -1178,6 +1251,7 @@ error = rtsTrue;
/* =========== TRACING ---------=================== */
case 'l':
OPTION_SAFE;
TRACING_BUILD_ONLY(
RtsFlags.TraceFlags.tracing = TRACE_EVENTLOG;
read_trace_flags(&rts_argv[arg][2]);
......@@ -1185,6 +1259,7 @@ error = rtsTrue;
break;
case 'v':
OPTION_SAFE;
DEBUG_BUILD_ONLY(
RtsFlags.TraceFlags.tracing = TRACE_STDERR;
read_trace_flags(&rts_argv[arg][2]);
......@@ -1196,11 +1271,13 @@ error = rtsTrue;
case 'x': /* Extend the argument space */
switch(rts_argv[arg][2]) {
case '\0':
OPTION_SAFE;
errorBelch("incomplete RTS option: %s",rts_argv[arg]);
error = rtsTrue;
break;
case 'b': /* heapBase in hex; undocumented */
OPTION_UNSAFE;
if (rts_argv[arg][3] != '\0') {
RtsFlags.GcFlags.heapBase
= strtol(rts_argv[arg]+3, (char **) NULL, 16);
......@@ -1212,6 +1289,7 @@ error = rtsTrue;
#if defined(x86_64_HOST_ARCH)
case 'm': /* linkerMemBase */
OPTION_UNSAFE;
if (rts_argv[arg][3] != '\0') {
RtsFlags.MiscFlags.linkerMemBase
= strtol(rts_argv[arg]+3, (char **) NULL, 16);
......@@ -1226,12 +1304,14 @@ error = rtsTrue;
#endif
case 'c': /* Debugging tool: show current cost centre on an exception */
OPTION_SAFE;
PROFILING_BUILD_ONLY(
RtsFlags.ProfFlags.showCCSOnException = rtsTrue;
);
break;
case 't': /* Include memory used by TSOs in a heap profile */
OPTION_SAFE;
PROFILING_BUILD_ONLY(
RtsFlags.ProfFlags.includeTSOs = rtsTrue;
);
......@@ -1240,6 +1320,7 @@ error = rtsTrue;
/* The option prefix '-xx' is reserved for future extension. KSW 1999-11. */
default:
OPTION_SAFE;
errorBelch("unknown RTS option: %s",rts_argv[arg]);
error = rtsTrue;
break;
......@@ -1248,10 +1329,18 @@ error = rtsTrue;
/* =========== OH DEAR ============================ */
default:
OPTION_SAFE;
errorBelch("unknown RTS option: %s",rts_argv[arg]);
error = rtsTrue;
break;
}
if (!option_checked) {
/* Naughty! Someone didn't use OPTION_UNSAFE / OPTION_SAFE for
an option above */
errorBelch("Internal error in the RTS options parser");
stg_exit(EXIT_FAILURE);
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment