Commit message: None
CVE: CVE-2016-3861
CWE: CWE-119
Vulnerability description: LibUtils in Android 4.x before 4.4.4, 5.0.x before 5.0.2, 5.1.x before 5.1.1, 6.x before 2016-09-01, and 7.0 before 2016-09-01 mishandles conversions between Unicode character encodings with different encoding widths, which allows remote attackers to execute arbitrary code or cause a denial of service (heap-based buffer overflow) via a crafted file, aka internal bug 29250543.

Project: Android

StringBuilder& StringBuilder::append(const StringPiece16& str) {
 if (!mError.empty()) {
 return *this;
 }

 const char16_t* const end = str.end();
 const char16_t* start = str.begin();
 const char16_t* current = start;
 while (current != end) {
 if (mLastCharWasEscape) {
 switch (*current) {
 case u't':
                    mStr += u'\t';
 break;
 case u'n':
                    mStr += u'\n';
 break;
 case u'#':
                    mStr += u'#';
 break;
 case u'@':
                    mStr += u'@';
 break;
 case u'?':
                    mStr += u'?';
 break;
 case u'"':
                    mStr += u'"';
 break;
 case u'\'':
                    mStr += u'\'';
 break;
 case u'\\':
                    mStr += u'\\';
 break;
 case u'u': {
                    current++;
 Maybe<char16_t> c = parseUnicodeCodepoint(&current, end);
 if (!c) {
                        mError = "invalid unicode escape sequence";
 return *this;
 }
                    mStr += c.value();
                    current -= 1;
 break;
 }

 default:
 break;
 }
            mLastCharWasEscape = false;
            start = current + 1;
 } else if (*current == u'"') {
 if (!mQuote && mTrailingSpace) {
 if (mTrailingSpace) {
 if (!mStr.empty()) {
                        mStr += u' ';
 }
                    mTrailingSpace = false;
 }
 }
            mQuote = !mQuote;
            mStr.append(start, current - start);
            start = current + 1;
 } else if (*current == u'\'' && !mQuote) {
            mError = "unescaped apostrophe";
 return *this;
 } else if (*current == u'\\') {
 if (!mQuote && mTrailingSpace) {
 if (!mStr.empty()) {
                    mStr += u' ';
 }
                mTrailingSpace = false;
 }
            mStr.append(start, current - start);
            start = current + 1;
            mLastCharWasEscape = true;
 } else if (!mQuote) {
 if (isspace16(*current)) {
 if (!mTrailingSpace) {
                    mTrailingSpace = true;
                    mStr.append(start, current - start);
 }

                start = current + 1;
 } else if (mTrailingSpace) {
 if (!mStr.empty()) {
                    mStr += u' ';
 }
                mTrailingSpace = false;
 }
 }
        current++;
 }
    mStr.append(start, end - start);
 return *this;
}

Project: Android

static size_t consumeDigits(const char16_t* start, const char16_t* end) {
 const char16_t* c = start;
 for (; c != end && *c >= u'0' && *c <= u'9'; c++) {}
 return static_cast<size_t>(c - start);
}

Project: Android

std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
    std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
 uint8_t* p = data.get();
 for (const auto& block : buffer) {
        memcpy(p, block.buffer.get(), block.size);
        p += block.size;
 }
 return data;
}

Project: Android

bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
 StringPiece16* outEntry, StringPiece16* outSuffix) {
 if (!stringStartsWith<char16_t>(path, u"res/")) {
 return false;
 }

 StringPiece16::const_iterator lastOccurence = path.end();
 for (auto iter = path.begin() + StringPiece16(u"res/").size(); iter != path.end(); ++iter) {
 if (*iter == u'/') {
            lastOccurence = iter;
 }
 }

 if (lastOccurence == path.end()) {
 return false;
 }

 auto iter = std::find(lastOccurence, path.end(), u'.');
 *outSuffix = StringPiece16(iter, path.end() - iter);
 *outEntry = StringPiece16(lastOccurence + 1, iter - lastOccurence - 1);
 *outPrefix = StringPiece16(path.begin(), lastOccurence - path.begin() + 1);
 return true;
}

Project: Android

bool isJavaClassName(const StringPiece16& str) {
 size_t pieces = 0;
 for (const StringPiece16& piece : tokenize(str, u'.')) {
        pieces++;
 if (piece.empty()) {
 return false;
 }

 if (piece.data()[0] == u'$' || piece.data()[piece.size() - 1] == u'$') {
 return false;
 }

 if (findNonAlphaNumericAndNotInSet(piece, u"$_") != piece.end()) {
 return false;
 }
 }
 return pieces >= 2;
}

Project: Android

bool isJavaPackageName(const StringPiece16& str) {
 if (str.empty()) {
 return false;
 }

 size_t pieces = 0;
 for (const StringPiece16& piece : tokenize(str, u'.')) {
        pieces++;
 if (piece.empty()) {
 return false;
 }

 if (piece.data()[0] == u'_' || piece.data()[piece.size() - 1] == u'_') {
 return false;
 }

 if (findNonAlphaNumericAndNotInSet(piece, u"_") != piece.end()) {
 return false;
 }
 }
 return pieces >= 1;
}

Project: Android

std::vector<std::string> split(const StringPiece& str, char sep) {
 return splitAndTransform(str, sep, nullptr);
}

Project: Android

std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
 return splitAndTransform(str, sep, ::tolower);
}

Project: Android

static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
 const std::function<char(char)>& f) {
    std::vector<std::string> parts;
 const StringPiece::const_iterator end = std::end(str);
 StringPiece::const_iterator start = std::begin(str);
 StringPiece::const_iterator current;
 do {
        current = std::find(start, end, sep);
        parts.emplace_back(str.substr(start, current).toString());
 if (f) {
            std::string& part = parts.back();
            std::transform(part.begin(), part.end(), part.begin(), f);
 }
        start = current + 1;
 } while (current != end);
 return parts;
}

Project: Android

StringPiece16 trimWhitespace(const StringPiece16& str) {
 if (str.size() == 0 || str.data() == nullptr) {
 return str;
 }

 const char16_t* start = str.data();
 const char16_t* end = str.data() + str.length();

 while (start != end && util::isspace16(*start)) {
        start++;
 }

 while (end != start && util::isspace16(*(end - 1))) {
        end--;
 }

 return StringPiece16(start, end - start);
}

Project: Android

StringPiece trimWhitespace(const StringPiece& str) {
 if (str.size() == 0 || str.data() == nullptr) {
 return str;
 }

 const char* start = str.data();
 const char* end = str.data() + str.length();

 while (start != end && isspace(*start)) {
        start++;
 }

 while (end != start && isspace(*(end - 1))) {
        end--;
 }

 return StringPiece(start, end - start);
}

Project: Android

std::u16string utf8ToUtf16(const StringPiece& utf8) {
 ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()),
            utf8.length());
 if (utf16Length <= 0) {
 return {};
 }

    std::u16string utf16;
    utf16.resize(utf16Length);
    utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin());
 return utf16;
}

Project: Android

bool verifyJavaStringFormat(const StringPiece16& str) {
 const char16_t* c = str.begin();
 const char16_t* const end = str.end();

 size_t argCount = 0;
 bool nonpositional = false;
 while (c != end) {
 if (*c == u'%' && c + 1 < end) {
            c++;

 if (*c == u'%') {
                c++;
 continue;
 }

            argCount++;

 size_t numDigits = consumeDigits(c, end);
 if (numDigits > 0) {
                c += numDigits;
 if (c != end && *c != u'$') {
                    nonpositional = true;
 }
 } else if (*c == u'<') {
                nonpositional = true;

                c++;

 if (c != end && *c == u'$') {
                    c++;
 }
 } else {
                nonpositional = true;
 }

 while (c != end && (*c == u'-' ||
 *c == u'#' ||
 *c == u'+' ||
 *c == u' ' ||
 *c == u',' ||
 *c == u'(' ||
 (*c >= u'0' && *c <= '9'))) {
                c++;
 }

 /*
             * This is a shortcut to detect strings that are going to Time.format()
             * instead of String.format()
             *
             * Comparison of String.format() and Time.format() args:
             *
             * String: ABC E GH  ST X abcdefgh  nost x
             *   Time:    DEFGHKMS W Za  d   hkm  s w yz
             *
             * Therefore we know it's definitely Time if we have:
             *     DFKMWZkmwyz
             */
 if (c != end) {
 switch (*c) {
 case 'D':
 case 'F':
 case 'K':
 case 'M':
 case 'W':
 case 'Z':
 case 'k':
 case 'm':
 case 'w':
 case 'y':
 case 'z':
 return true;
 }
 }
 }

 if (c != end) {
            c++;
 }
 }

 if (argCount > 1 && nonpositional) {
 return false;
 }
 return true;
}

Project: Android

bool writeAll(std::ostream& out, const BigBuffer& buffer) {
 for (const auto& b : buffer) {
 if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
 return false;
 }
 }
 return true;
}