From 5e7ee8f33e088c9e2db7005abc5eb39a4afd9cd9 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Tue, 4 Jun 2013 02:06:40 +0000 Subject: [PATCH] - Addded support for multi-line values in INI files, so you can't maliciously inject stray newline characters into the config file using ACS's SetCVarString. SVN r4326 (trunk) --- src/configfile.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++++- src/configfile.h | 2 + 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/configfile.cpp b/src/configfile.cpp index fcbed42c3..b8e37d91c 100644 --- a/src/configfile.cpp +++ b/src/configfile.cpp @@ -38,9 +38,12 @@ #include "doomtype.h" #include "configfile.h" +#include "m_random.h" #define READBUFFERSIZE 256 +static FRandom pr_endtag; + //==================================================================== // // FConfigFile Constructor @@ -679,13 +682,68 @@ bool FConfigFile::ReadConfig (void *file) whiteprobe++; } *(whiteprobe - 1) = 0; - NewConfigEntry (section, start, whiteprobe); + // Check for multi-line value + if (whiteprobe[0] == '<' && whiteprobe[1] == '<' && whiteprobe[2] == '<' && whiteprobe[3] != '\0') + { + ReadMultiLineValue (file, section, start, whiteprobe + 3); + } + else + { + NewConfigEntry (section, start, whiteprobe); + } } } } return true; } +//==================================================================== +// +// FConfigFile :: ReadMultiLineValue +// +// Reads a multi-line value, with format as follows: +// +// key=<<>>ENDTAG +// +// The final ENDTAG must be on a line all by itself. +// +//==================================================================== + +FConfigFile::FConfigEntry *FConfigFile::ReadMultiLineValue(void *file, FConfigSection *section, const char *key, const char *endtag) +{ + char readbuf[READBUFFERSIZE]; + FString value; + size_t endlen = strlen(endtag); + + // Keep on reading lines until we reach a line that matches >>>endtag + while (ReadLine(readbuf, READBUFFERSIZE, file) != NULL) + { + // Does the start of this line match the endtag? + if (readbuf[0] == '>' && readbuf[1] == '>' && readbuf[2] == '>' && + strncmp(readbuf + 3, endtag, endlen) == 0) + { // Is there nothing but line break characters after the match? + size_t i; + for (i = endlen + 3; readbuf[i] != '\0'; ++i) + { + if (readbuf[i] != '\n' && readbuf[i] != '\r') + { // Not a line break character + break; + } + } + if (readbuf[i] == '\0') + { // We're done; strip the previous line's line breaks, since it's not part of the value. + value.StripRight("\n\r"); + } + break; + } + // Append this line to the value. + value << readbuf; + } + return NewConfigEntry(section, key, value); +} + //==================================================================== // // FConfigFile :: ReadLine @@ -732,7 +790,16 @@ bool FConfigFile::WriteConfigFile () const fprintf (file, "[%s]\n", section->Name); while (entry != NULL) { - fprintf (file, "%s=%s\n", entry->Key, entry->Value); + if (strpbrk(entry->Value, "\r\n") == NULL) + { // Single-line value + fprintf (file, "%s=%s\n", entry->Key, entry->Value); + } + else + { // Multi-line value + const char *endtag = GenerateEndTag(entry->Value); + fprintf (file, "%s=<<<%s\n%s\n>>>%s\n", entry->Key, + endtag, entry->Value, endtag); + } entry = entry->Next; } section = section->Next; @@ -742,6 +809,44 @@ bool FConfigFile::WriteConfigFile () const return true; } +//==================================================================== +// +// FConfigFile :: GenerateEndTag +// +// Generates a terminator sequence for multi-line values that does +// not appear anywhere in the value. +// +//==================================================================== + +const char *FConfigFile::GenerateEndTag(const char *value) +{ + static const char Base64Table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; + static char EndTag[25] = "EOV-"; + + // Try different 20-character sequences until we find one that + // isn't in the value. We create the sequences by generating two + // 64-bit random numbers and Base64 encoding the first 15 bytes + // from them. + union { QWORD rand_num[2]; BYTE rand_bytes[16]; }; + do + { + rand_num[0] = pr_endtag.GenRand64(); + rand_num[1] = pr_endtag.GenRand64(); + + for (int i = 0; i < 5; ++i) + { + DWORD three_bytes = (rand_bytes[i*3] << 16) | (rand_bytes[i*3+1] << 8) | (rand_bytes[i*3+2]); + EndTag[4+i*4 ] = Base64Table[rand_bytes[i*3] >> 2]; + EndTag[4+i*4+1] = Base64Table[((rand_bytes[i*3] & 3) << 4) | (rand_bytes[i*3+1] >> 4)]; + EndTag[4+i*4+2] = Base64Table[((rand_bytes[i*3+1] & 15) << 2) | (rand_bytes[i*3+2] >> 6)]; + EndTag[4+i*4+3] = Base64Table[rand_bytes[i*3+2] & 63]; + } + } + while (strstr(value, EndTag) != NULL); + return EndTag; +} + //==================================================================== // // FConfigFile :: WriteCommentHeader diff --git a/src/configfile.h b/src/configfile.h index b704dd359..25c366f01 100644 --- a/src/configfile.h +++ b/src/configfile.h @@ -78,6 +78,7 @@ protected: virtual char *ReadLine (char *string, int n, void *file) const; bool ReadConfig (void *file); + static const char *GenerateEndTag(const char *value); bool OkayToWrite; bool FileExisted; @@ -110,6 +111,7 @@ private: FConfigEntry *FindEntry (FConfigSection *section, const char *key) const; FConfigSection *NewConfigSection (const char *name); FConfigEntry *NewConfigEntry (FConfigSection *section, const char *key, const char *value); + FConfigEntry *ReadMultiLineValue (void *file, FConfigSection *section, const char *key, const char *terminator); void SetSectionNote (FConfigSection *section, const char *note); public: