Commit message: None
CVE: CVE-2015-6786
CWE: CWE-264
Vulnerability description: The CSPSourceList::matches function in WebKit/Source/core/frame/csp/CSPSourceList.cpp in the Content Security Policy (CSP) implementation in Google Chrome before 47.0.2526.73 accepts a blob:, data:, or filesystem: URL as a match for a * pattern, which allows remote attackers to bypass intended scheme restrictions in opportunistic circumstances by leveraging a policy that relies on this pattern.

Project: Chrome

bool CSPHandler::AlwaysParseForType(Manifest::Type type) const {
  if (is_platform_app_)
    return type == Manifest::TYPE_PLATFORM_APP;
  else
    return type == Manifest::TYPE_EXTENSION ||
        type == Manifest::TYPE_LEGACY_PACKAGED_APP;
}

Project: Chrome

CSPHandler::CSPHandler(bool is_platform_app)
    : is_platform_app_(is_platform_app) {
}

Project: Chrome

CSPInfo::CSPInfo(const std::string& security_policy)
    : content_security_policy(security_policy) {
}

Project: Chrome

 int GetValidatorOptions(Extension* extension) {
   int options = csp_validator::OPTIONS_NONE;

  if (extension->GetType() == Manifest::TYPE_EXTENSION ||
      extension->GetType() == Manifest::TYPE_LEGACY_PACKAGED_APP) {
    options |= csp_validator::OPTIONS_ALLOW_UNSAFE_EVAL;
  }

  if (extensions::Manifest::IsComponentLocation(extension->location()))
    options |= csp_validator::OPTIONS_ALLOW_INSECURE_OBJECT_SRC;

  return options;
}

Project: Chrome

const std::vector<std::string> CSPHandler::Keys() const {
  const std::string& key = is_platform_app_ ?
      keys::kPlatformAppContentSecurityPolicy : keys::kContentSecurityPolicy;
  return SingleKey(key);
}

Project: Chrome

bool CSPHandler::Parse(Extension* extension, base::string16* error) {
  const std::string key = Keys()[0];
  if (!extension->manifest()->HasPath(key)) {
    if (extension->manifest_version() >= 2) {
      std::string content_security_policy = is_platform_app_ ?
          kDefaultPlatformAppContentSecurityPolicy :
          kDefaultContentSecurityPolicy;

      CHECK_EQ(content_security_policy,
               SanitizeContentSecurityPolicy(content_security_policy,
                                             GetValidatorOptions(extension),
                                             NULL));
      extension->SetManifestData(keys::kContentSecurityPolicy,
                                 new CSPInfo(content_security_policy));
    }
    return true;
  }

  std::string content_security_policy;
  if (!extension->manifest()->GetString(key, &content_security_policy)) {
    *error = base::ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
    return false;
  }
  if (!ContentSecurityPolicyIsLegal(content_security_policy)) {
    *error = base::ASCIIToUTF16(errors::kInvalidContentSecurityPolicy);
    return false;
  }
  std::string sanitized_csp;
  if (extension->manifest_version() >= 2) {
    std::vector<InstallWarning> warnings;
    content_security_policy =
        SanitizeContentSecurityPolicy(content_security_policy,
                                      GetValidatorOptions(extension),
                                      &warnings);
    extension->AddInstallWarnings(warnings);
  }

  extension->SetManifestData(keys::kContentSecurityPolicy,
                             new CSPInfo(content_security_policy));
  return true;
}

Project: Chrome

CSPHandler::~CSPHandler() {
}

Project: Chrome

CSPInfo::~CSPInfo() {
}

Project: Chrome

CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName)
    : m_policy(policy)
    , m_directiveName(directiveName)
    , m_allowSelf(false)
    , m_allowStar(false)
    , m_allowInline(false)
    , m_allowEval(false)
    , m_hashAlgorithmsUsed(0)
{
}

Project: Chrome

void CSPSourceList::addSourceHash(const ContentSecurityPolicyHashAlgorithm& algorithm, const DigestValue& hash)
{
    m_hashes.add(CSPHashValue(algorithm, hash));
     m_hashAlgorithmsUsed |= algorithm;
 }

Project: Chrome

void CSPSourceList::addSourceNonce(const String& nonce)
{
    m_nonces.add(nonce);
}

Project: Chrome

void CSPSourceList::addSourceSelf()
{
    m_allowSelf = true;
}

Project: Chrome

void CSPSourceList::addSourceStar()
{
    m_allowStar = true;
}

Project: Chrome

void CSPSourceList::addSourceUnsafeEval()
{
    m_allowEval = true;
}

Project: Chrome

bool CSPSourceList::allowEval() const
{
    return m_allowEval;
}

Project: Chrome

bool CSPSourceList::allowHash(const CSPHashValue& hashValue) const
{
    return m_hashes.contains(hashValue);
}

Project: Chrome

 bool CSPSourceList::allowInline() const
{
    return m_allowInline;
}

Project: Chrome

uint8_t CSPSourceList::hashAlgorithmsUsed() const
{
    return m_hashAlgorithmsUsed;
}

Project: Chrome

bool CSPSourceList::isHashOrNoncePresent() const
{
    return !m_nonces.isEmpty() || m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone;
}

Project: Chrome

static bool isSourceListNone(const UChar* begin, const UChar* end)
{
    skipWhile<UChar, isASCIISpace>(begin, end);

    const UChar* position = begin;
    skipWhile<UChar, isSourceCharacter>(position, end);
    if (!equalIgnoringCase("'none'", begin, position - begin))
        return false;

    skipWhile<UChar, isASCIISpace>(position, end);
    if (position != end)
        return false;

    return true;
}

Project: Chrome

void CSPSourceList::parse(const UChar* begin, const UChar* end)
{
    if (isSourceListNone(begin, end))
        return;

    const UChar* position = begin;
    while (position < end) {
        skipWhile<UChar, isASCIISpace>(position, end);
        if (position == end)
            return;

        const UChar* beginSource = position;
        skipWhile<UChar, isSourceCharacter>(position, end);

        String scheme, host, path;
        int port = 0;
        CSPSource::WildcardDisposition hostWildcard = CSPSource::NoWildcard;
        CSPSource::WildcardDisposition portWildcard = CSPSource::NoWildcard;

        if (parseSource(beginSource, position, scheme, host, port, path, hostWildcard, portWildcard)) {
            if (scheme.isEmpty() && host.isEmpty())
                continue;
            if (m_policy->isDirectiveName(host))
                m_policy->reportDirectiveAsSourceExpression(m_directiveName, host);
            m_list.append(CSPSource(m_policy, scheme, host, port, path, hostWildcard, portWildcard));
        } else {
            m_policy->reportInvalidSourceExpression(m_directiveName, String(beginSource, position - beginSource));
        }

        ASSERT(position == end || isASCIISpace(*position));
    }
}

Project: Chrome

bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm)
{
    static const struct {
        const char* prefix;
        ContentSecurityPolicyHashAlgorithm type;
    } kSupportedPrefixes[] = {
        { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 },
        { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 },
        { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 },
        { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 },
        { "'sha-256-", ContentSecurityPolicyHashAlgorithmSha256 },
        { "'sha-384-", ContentSecurityPolicyHashAlgorithmSha384 },
        { "'sha-512-", ContentSecurityPolicyHashAlgorithmSha512 }
    };

    String prefix;
    hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone;
    size_t hashLength = end - begin;

    for (const auto& algorithm : kSupportedPrefixes) {
        if (hashLength > strlen(algorithm.prefix) && equalIgnoringCase(algorithm.prefix, begin, strlen(algorithm.prefix))) {
            prefix = algorithm.prefix;
            hashAlgorithm = algorithm.type;
            break;
        }
    }

    if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone)
        return true;

    const UChar* position = begin + prefix.length();
    const UChar* hashBegin = position;

    ASSERT(position < end);
    skipWhile<UChar, isBase64EncodedCharacter>(position, end);
    ASSERT(hashBegin <= position);

    if (position < end)
        skipExactly<UChar>(position, position + 1, '=');
    if (position < end)
        skipExactly<UChar>(position, position + 1, '=');

    if (position + 1 != end || *position != '\'' || position == hashBegin)
        return false;

    Vector<char> hashVector;
    base64Decode(normalizeToBase64(String(hashBegin, position - hashBegin)), hashVector);
    if (hashVector.size() > kMaxDigestSize)
        return false;
    hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size());
    return true;
}

Project: Chrome

bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, CSPSource::WildcardDisposition& hostWildcard)
{
    ASSERT(begin <= end);
    ASSERT(host.isEmpty());
    ASSERT(hostWildcard == CSPSource::NoWildcard);

    if (begin == end)
        return false;

    const UChar* position = begin;

    if (skipExactly<UChar>(position, end, '*')) {
        hostWildcard = CSPSource::HasWildcard;

        if (position == end)
            return true;

        if (!skipExactly<UChar>(position, end, '.'))
            return false;
    }

    const UChar* hostBegin = position;

    while (position < end) {
        if (!skipExactly<UChar, isHostCharacter>(position, end))
            return false;

        skipWhile<UChar, isHostCharacter>(position, end);

        if (position < end && !skipExactly<UChar>(position, end, '.'))
            return false;
    }

    ASSERT(position == end);
    host = String(hostBegin, end - hostBegin);
    return true;
}

Project: Chrome

bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& nonce)
{
    size_t nonceLength = end - begin;
    const char* prefix = "'nonce-";

    if (nonceLength <= strlen(prefix) || !equalIgnoringCase(prefix, begin, strlen(prefix)))
        return true;

    const UChar* position = begin + strlen(prefix);
    const UChar* nonceBegin = position;

    ASSERT(position < end);
    skipWhile<UChar, isNonceCharacter>(position, end);
    ASSERT(nonceBegin <= position);

    if (position + 1 != end || *position != '\'' || position == nonceBegin)
        return false;

    nonce = String(nonceBegin, position - nonceBegin);
    return true;
}

Project: Chrome

bool CSPSourceList::parsePath(const UChar* begin, const UChar* end, String& path)
{
    ASSERT(begin <= end);
    ASSERT(path.isEmpty());

    const UChar* position = begin;
    skipWhile<UChar, isPathComponentCharacter>(position, end);
    if (position < end)
        m_policy->reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position);

    path = decodeURLEscapeSequences(String(begin, position - begin));

    ASSERT(position <= end);
    ASSERT(position == end || (*position == '#' || *position == '?'));
    return true;
}

Project: Chrome

bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, CSPSource::WildcardDisposition& portWildcard)
{
    ASSERT(begin <= end);
    ASSERT(!port);
    ASSERT(portWildcard == CSPSource::NoWildcard);

    if (!skipExactly<UChar>(begin, end, ':'))
        ASSERT_NOT_REACHED();

    if (begin == end)
        return false;

    if (end - begin == 1 && *begin == '*') {
        port = 0;
        portWildcard = CSPSource::HasWildcard;
        return true;
    }

    const UChar* position = begin;
    skipWhile<UChar, isASCIIDigit>(position, end);

    if (position != end)
        return false;

    bool ok;
    port = charactersToIntStrict(begin, end - begin, &ok);
    return ok;
}

Project: Chrome

bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
{
    ASSERT(begin <= end);
    ASSERT(scheme.isEmpty());

    if (begin == end)
        return false;

    const UChar* position = begin;

    if (!skipExactly<UChar, isASCIIAlpha>(position, end))
        return false;

    skipWhile<UChar, isSchemeContinuationCharacter>(position, end);

    if (position != end)
        return false;

    scheme = String(begin, end - begin);
    return true;
}

Project: Chrome

    CSPSourceListTest()
        : csp(ContentSecurityPolicy::create())
    {
    }

Project: Chrome

    virtual void SetUp()
    {
        KURL secureURL(ParsedURLString, "https://example.test/image.png");
        RefPtr<SecurityOrigin> secureOrigin(SecurityOrigin::create(secureURL));
        document = Document::create();
        document->setSecurityOrigin(secureOrigin);
        csp->bindToExecutionContext(document.get());
    }

Project: Chrome

static void parseSourceList(CSPSourceList& sourceList, String& sources)
{
    Vector<UChar> characters;
    sources.appendTo(characters);
    sourceList.parse(characters.data(), characters.data() + characters.size());
}