Commit message: Fix buffer overflow (pull request #81)
CVE: CVE-2015-4590
CWE: CWE-415
Vulnerability description: The extractFrom function in Internals/QuotedString.cpp in Arduino JSON before 4.5 allows remote attackers to cause a denial of service (crash) via a JSON string with a \ (backslash) followed by a terminator, as demonstrated by "\\\0", which triggers a buffer overflow and over-read.

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedBackspace) {
  whenInputIs("\"hello \\bworld\\b\"");
  resultMustBe("hello \bworld\b");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedFormfeed) {
  whenInputIs("\"hello \\fworld\\f\"");
  resultMustBe("hello \fworld\f");
}

Project: ArduinoJson
File: QuotedString.cpp

static inline char getSpecialChar(char c) {
  // Optimized for code size on a 8-bit AVR

  const char *p = specialChars;

  while (p[0] && p[1] != c) {
    p += 2;
  }

  return p[0];
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedNewline) {
  whenInputIs("\"hello \\nworld\\n\"");
  resultMustBe("hello \nworld\n");
}

Project: ArduinoJson
File: QuotedString.cpp

size_t QuotedString::printTo(const char *s, Print &p) {
  if (!s) return p.print("null");

  size_t n = p.write('\"');

  while (*s) {
    n += printCharTo(*s++, p);
  }

  return n + p.write('\"');
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, UnterminatedEscapeSequence) {
  whenInputIs("\"\\\0\"", 4);
  resultMustBe(0);
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedTab) {
  whenInputIs("\"hello \\tworld\\t\"");
  resultMustBe("hello \tworld\t");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedSingleQuote) {
  whenInputIs("\"hello \\\'world\\\'\"");
  resultMustBe("hello 'world'");
}

Project: ArduinoJson
File: QuotedString.cpp

static inline size_t printCharTo(char c, Print &p) {
  char specialChar = getSpecialChar(c);

  return specialChar ? p.write('\\') + p.write(specialChar) : p.write(c);
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, DoubleQuotedStringWithTrailing) {
  whenInputIs("\"hello\" world");

  resultMustBe("hello");
  trailingMustBe(" world");
}

Project: ArduinoJson
File: QuotedString.cpp

static char unescapeChar(char c) {
  // Optimized for code size on a 8-bit AVR

  const char *p = specialChars + 4;

  for (;;) {
    if (p[0] == '\0') return c;
    if (p[0] == c) return p[1];
    p += 2;
  }
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedSolidus) {
  whenInputIs("\"hello \\/world\\/\"");
  resultMustBe("hello /world/");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedCarriageReturn) {
  whenInputIs("\"hello \\rworld\\r\"");
  resultMustBe("hello \rworld\r");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedReverseSolidus) {
  whenInputIs("\"hello \\\\world\\\\\"");
  resultMustBe("hello \\world\\");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EscapedDoubleQuote) {
  whenInputIs("\"hello \\\"world\\\"\"");
  resultMustBe("hello \"world\"");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, SingleQuotedStringWithTrailing) {
  whenInputIs("'hello' world");

  resultMustBe("hello");
  trailingMustBe(" world");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

  void whenInputIs(const char *json, size_t len) {
    memcpy(_jsonString, json, len);
    _result = QuotedString::extractFrom(_jsonString, &_trailing);
  }

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, NoQuotes) {
  whenInputIs("hello world");

  resultMustBe(0);
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EmptySingleQuotedString) {
  whenInputIs("''");

  resultMustBe("");
  trailingMustBe("");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, CurlyBraces) {
  whenInputIs("\"{hello:world}\"");
  resultMustBe("{hello:world}");
}

Project: ArduinoJson
File: QuotedString.cpp

char *QuotedString::extractFrom(char *input, char **endPtr) {
  char *startPtr = input + 1;  // skip the quote
  char *readPtr = startPtr;
  char *writePtr = startPtr;
  char c;

  char firstChar = *input;
  char stopChar = firstChar;  // closing quote is the same as opening quote

  if (!isQuote(firstChar)) goto ERROR_OPENING_QUOTE_MISSING;

  for (;;) {
    c = *readPtr++;

    if (c == '\0') goto ERROR_CLOSING_QUOTE_MISSING;

    if (c == stopChar) goto SUCCESS;

    if (c == '\\') {
      // replace char
      c = unescapeChar(*readPtr++);
      if (c == '\0') goto ERROR_ESCAPE_SEQUENCE_INTERRUPTED;
    }

    *writePtr++ = c;
  }

SUCCESS:
  // end the string here
  *writePtr = '\0';

  // update end ptr
  *endPtr = readPtr;

  // return pointer to unquoted string
  return startPtr;

ERROR_OPENING_QUOTE_MISSING:
ERROR_CLOSING_QUOTE_MISSING:
ERROR_ESCAPE_SEQUENCE_INTERRUPTED:
  return NULL;
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

  void resultMustBe(const char *expected) { EXPECT_STREQ(expected, _result); }

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, AllEscapedCharsTogether) {
  whenInputIs("\"1\\\"2\\\\3\\/4\\b5\\f6\\n7\\r8\\t9\"");
  resultMustBe("1\"2\\3/4\b5\f6\n7\r8\t9");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, SquareBraquets) {
  whenInputIs("\"[hello,world]\"");
  resultMustBe("[hello,world]");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

  void whenInputIs(const char *json) {
    strcpy(_jsonString, json);
    _result = QuotedString::extractFrom(_jsonString, &_trailing);
  }

Project: ArduinoJson
File: QuotedString.cpp

static inline bool isQuote(char c) { return c == '\"' || c == '\''; }

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, SimpleDoubleQuotedString) {
  whenInputIs("\"hello world\"");

  resultMustBe("hello world");
  trailingMustBe("");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, MissingClosingQuote) {
  whenInputIs("\"hello world");

  resultMustBe(0);
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

TEST_F(QuotedString_ExtractFrom_Tests, EmptyDoubleQuotedString) {
  whenInputIs("\"\"");

  resultMustBe("");
  trailingMustBe("");
}

Project: ArduinoJson
File: QuotedString_ExtractFrom_Tests.cpp

  void trailingMustBe(const char *expected) {
    EXPECT_STREQ(expected, _trailing);
  }