Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(54)

Side by Side Diff: Source/core/frame/SubresourceIntegrity.cpp

Issue 1126343003: Ignore unknown options to subresource integrity (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Fixed some edge cases Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "config.h" 5 #include "config.h"
6 #include "core/frame/SubresourceIntegrity.h" 6 #include "core/frame/SubresourceIntegrity.h"
7 7
8 #include "core/HTMLNames.h" 8 #include "core/HTMLNames.h"
9 #include "core/dom/Document.h" 9 #include "core/dom/Document.h"
10 #include "core/dom/Element.h" 10 #include "core/dom/Element.h"
(...skipping 17 matching lines...) Expand all
28 namespace blink { 28 namespace blink {
29 29
30 // FIXME: This should probably use common functions with ContentSecurityPolicy. 30 // FIXME: This should probably use common functions with ContentSecurityPolicy.
31 static bool isIntegrityCharacter(UChar c) 31 static bool isIntegrityCharacter(UChar c)
32 { 32 {
33 // Check if it's a base64 encoded value. We're pretty loose here, as there's 33 // Check if it's a base64 encoded value. We're pretty loose here, as there's
34 // not much risk in it, and it'll make it simpler for developers. 34 // not much risk in it, and it'll make it simpler for developers.
35 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || c == '/ ' || c == '='; 35 return isASCIIAlphanumeric(c) || c == '_' || c == '-' || c == '+' || c == '/ ' || c == '=';
36 } 36 }
37 37
38 static bool isTypeCharacter(UChar c) 38 static bool isOptionNameCharacter(UChar c)
39 { 39 {
40 return isASCIIAlphanumeric(c) || c == '+' || c == '.' || c == '-'; 40 return isASCIIAlphanumeric(c) || c == '-';
41 }
42
43 static bool isOptionValueCharacter(UChar c)
44 {
45 return isASCIIAlphanumeric(c) || c == '!' || c == '#' || c == '$' || c == '% ' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~' || c == '/';
Mike West 2015/05/11 03:25:29 This is a strangely arbitrary list of characters.
jww 2015/05/11 05:39:07 Fair point. As per fmarier's suggestion in https:/
41 } 46 }
42 47
43 static void logErrorToConsole(const String& message, Document& document) 48 static void logErrorToConsole(const String& message, Document& document)
44 { 49 {
45 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err orMessageLevel, message)); 50 document.addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, Err orMessageLevel, message));
46 } 51 }
47 52
48 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2) 53 static bool DigestsEqual(const DigestValue& digest1, const DigestValue& digest2)
49 { 54 {
50 if (digest1.size() != digest2.size()) 55 if (digest1.size() != digest2.size())
51 return false; 56 return false;
52 57
53 for (size_t i = 0; i < digest1.size(); i++) { 58 for (size_t i = 0; i < digest1.size(); i++) {
54 if (digest1[i] != digest2[i]) 59 if (digest1[i] != digest2[i])
55 return false; 60 return false;
56 } 61 }
57 62
58 return true; 63 return true;
59 } 64 }
60 65
61 static String digestToString(const DigestValue& digest) 66 static String digestToString(const DigestValue& digest)
62 { 67 {
63 // We always output base64url encoded data, even though we use base64 intern ally. 68 // We always output base64url encoded data, even though we use base64 intern ally.
64 return base64URLEncode(reinterpret_cast<const char*>(digest.data()), digest. size(), Base64DoNotInsertLFs); 69 return base64URLEncode(reinterpret_cast<const char*>(digest.data()), digest. size(), Base64DoNotInsertLFs);
65 } 70 }
66 71
67 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con st String& source, const KURL& resourceUrl, const String& resourceType, const Re source& resource) 72 bool SubresourceIntegrity::CheckSubresourceIntegrity(const Element& element, con st String& source, const KURL& resourceUrl, const Resource& resource)
68 { 73 {
69 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled()) 74 if (!RuntimeEnabledFeatures::subresourceIntegrityEnabled())
70 return true; 75 return true;
71 76
72 if (!element.fastHasAttribute(HTMLNames::integrityAttr)) 77 if (!element.fastHasAttribute(HTMLNames::integrityAttr))
73 return true; 78 return true;
74 79
75 Document& document = element.document(); 80 Document& document = element.document();
76 81
77 if (!resource.isEligibleForIntegrityCheck(document.securityOrigin())) { 82 if (!resource.isEligibleForIntegrityCheck(document.securityOrigin())) {
(...skipping 16 matching lines...) Expand all
94 for (IntegrityMetadata& metadata : metadataList) { 99 for (IntegrityMetadata& metadata : metadataList) {
95 digest.clear(); 100 digest.clear();
96 bool digestSuccess = computeDigest(metadata.algorithm, normalizedSource. data(), normalizedSource.length(), digest); 101 bool digestSuccess = computeDigest(metadata.algorithm, normalizedSource. data(), normalizedSource.length(), digest);
97 102
98 if (digestSuccess) { 103 if (digestSuccess) {
99 Vector<char> hashVector; 104 Vector<char> hashVector;
100 base64Decode(metadata.digest, hashVector); 105 base64Decode(metadata.digest, hashVector);
101 DigestValue convertedHashVector; 106 DigestValue convertedHashVector;
102 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.dat a()), hashVector.size()); 107 convertedHashVector.append(reinterpret_cast<uint8_t*>(hashVector.dat a()), hashVector.size());
103 108
104 if (DigestsEqual(digest, convertedHashVector)) { 109 if (DigestsEqual(digest, convertedHashVector))
105 String& type = metadata.type; 110 return true;
106 if (!type.isEmpty() && !equalIgnoringCase(type, resourceType))
107 UseCounter::count(document, UseCounter::SRIElementWithNonMat chingIntegrityType);
108 else
109 return true;
110 }
111 } 111 }
112 } 112 }
113 113
114 if (computeDigest(HashAlgorithmSha256, normalizedSource.data(), normalizedSo urce.length(), digest)) { 114 if (computeDigest(HashAlgorithmSha256, normalizedSource.data(), normalizedSo urce.length(), digest)) {
115 // This message exposes the digest of the resource to the console. 115 // This message exposes the digest of the resource to the console.
116 // Because this is only to the console, that's okay for now, but we 116 // Because this is only to the console, that's okay for now, but we
117 // need to be very careful not to expose this in exceptions or 117 // need to be very careful not to expose this in exceptions or
118 // JavaScript, otherwise it risks exposing information about the 118 // JavaScript, otherwise it risks exposing information about the
119 // resource cross-origin. 119 // resource cross-origin.
120 logErrorToConsole("Failed to find a valid digest with matching content-t ype in the 'integrity' attribute for resource '" + resourceUrl.elidedString() + "' with computed SHA-256 integrity '" + digestToString(digest) + "'. The resourc e has been blocked.", document); 120 logErrorToConsole("Failed to find a valid digest in the 'integrity' attr ibute for resource '" + resourceUrl.elidedString() + "' with computed SHA-256 in tegrity '" + digestToString(digest) + "'. The resource has been blocked.", docum ent);
121 } else { 121 } else {
122 logErrorToConsole("There was an error computing an integrity value for r esource '" + resourceUrl.elidedString() + "'. The resource has been blocked.", d ocument); 122 logErrorToConsole("There was an error computing an integrity value for r esource '" + resourceUrl.elidedString() + "'. The resource has been blocked.", d ocument);
123 } 123 }
124 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt tribute); 124 UseCounter::count(document, UseCounter::SRIElementWithNonMatchingIntegrityAt tribute);
125 return false; 125 return false;
126 } 126 }
127 127
128 // Before: 128 // Before:
129 // 129 //
130 // [algorithm]-[hash] 130 // [algorithm]-[hash]
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
196 if (position == begin || (position != end && *position != '?')) { 196 if (position == begin || (position != end && *position != '?')) {
197 digest = emptyString(); 197 digest = emptyString();
198 return false; 198 return false;
199 } 199 }
200 200
201 // We accept base64url encoding, but normalize to "normal" base64 internally : 201 // We accept base64url encoding, but normalize to "normal" base64 internally :
202 digest = normalizeToBase64(String(begin, position - begin)); 202 digest = normalizeToBase64(String(begin, position - begin));
203 return true; 203 return true;
204 } 204 }
205 205
206
207 // Before: 206 // Before:
208 // 207 //
209 // [algorithm]-[hash] OR [algorithm]-[hash]?[options] 208 // [algorithm]-[hash] OR [algorithm]-[hash]?[name]=[value] OR [algorithm]-[hash]?[name]=[value]?[rest of options]
Mike West 2015/05/11 03:25:29 This is strange syntax. I'm not going to bikeshed
jww 2015/05/11 05:39:07 Bikeshedding irrelevant in my latest pull request
210 // ^ ^ ^ 209 // ^ ^ ^ ^ ^
211 // position/end position end 210 // position/end position end position end
212 // 211 //
213 // After (if successful: if the method returns false, we make no promises and th e caller should exit early): 212 // After (if successful: if the method returns false, we make no promises and th e caller should exit early):
214 // 213 //
215 // [algorithm]-[hash] OR [algorithm]-[hash]?[options] 214 // [algorithm]-[hash] OR [algorithm]-[hash]?[name]=[value] OR [algorithm]-[hash]?[name]=[value]?[rest of options]
216 // ^ ^ 215 // ^ ^ ^ ^
217 // position/end position/end 216 // position/end position/end position end
218 bool SubresourceIntegrity::parseMimeType(const UChar*& position, const UChar* en d, String& type) 217 bool SubresourceIntegrity::parseOption(const UChar*& position, const UChar* end, String& name, String& value)
219 { 218 {
220 type = emptyString(); 219 name = emptyString();
220 value = emptyString();
221 221
222 if (position == end) 222 if (position == end)
223 return true; 223 return true;
224 224
225 if (!skipToken<UChar>(position, end, "?ct=")) 225 if (!skipExactly<UChar>(position, end, '?'))
226 return false; 226 return false;
227 227
228 const UChar* begin = position; 228 const UChar* begin = position;
229 skipWhile<UChar, isASCIIAlpha>(position, end); 229 skipWhile<UChar, isOptionNameCharacter>(position, end);
230 if (position == end) 230 if (position == begin || position == end)
231 return false; 231 return false;
232 232
233 if (!skipExactly<UChar>(position, end, '/')) 233 name = String(begin, position - begin);
Mike West 2015/05/11 03:25:29 This means `name` will be set to some value, even
jww 2015/05/11 05:39:07 Also irrelevant given the broader changes, but in
234
235 if (!skipExactly<UChar>(position, end, '='))
234 return false; 236 return false;
235 237
236 if (position == end) 238 begin = position;
237 return false; 239 skipWhile<UChar, isOptionValueCharacter>(position, end);
238 240
239 skipWhile<UChar, isTypeCharacter>(position, end); 241 value = String(begin, position - begin);
240 if (position != end)
241 return false;
242 242
243 type = String(begin, position - begin); 243 if (position == end || *position == '?')
Mike West 2015/05/11 03:25:29 Nit: Please ASSERT that `position` is in-range bef
jww 2015/05/11 05:39:07 Also irrelevant now.
244 return true; 244 return true;
245
246 return false;
245 } 247 }
246 248
247 SubresourceIntegrity::IntegrityParseResult SubresourceIntegrity::parseIntegrityA ttribute(const WTF::String& attribute, WTF::Vector<IntegrityMetadata>& metadataL ist, Document& document) 249 SubresourceIntegrity::IntegrityParseResult SubresourceIntegrity::parseIntegrityA ttribute(const WTF::String& attribute, WTF::Vector<IntegrityMetadata>& metadataL ist, Document& document)
248 { 250 {
249 Vector<UChar> characters; 251 Vector<UChar> characters;
250 attribute.stripWhiteSpace().appendTo(characters); 252 attribute.stripWhiteSpace().appendTo(characters);
251 const UChar* position = characters.data(); 253 const UChar* position = characters.data();
252 const UChar* end = characters.end(); 254 const UChar* end = characters.end();
253 const UChar* currentIntegrityEnd; 255 const UChar* currentIntegrityEnd;
254 256
255 metadataList.clear(); 257 metadataList.clear();
256 bool error = false; 258 bool error = false;
257 259
258 // The integrity attribute takes the form: 260 // The integrity attribute takes the form:
259 // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP 261 // *WSP hash-with-options *( 1*WSP hash-with-options ) *WSP / *WSP
260 // To parse this, break on whitespace, parsing each algorithm/digest/mime 262 // To parse this, break on whitespace, parsing each algorithm/digest/option
261 // type in order. 263 // in order.
262 while (position < end) { 264 while (position < end) {
263 WTF::String digest; 265 WTF::String digest;
264 HashAlgorithm algorithm; 266 HashAlgorithm algorithm;
265 WTF::String type;
266 267
267 skipWhile<UChar, isASCIISpace>(position, end); 268 skipWhile<UChar, isASCIISpace>(position, end);
268 currentIntegrityEnd = position; 269 currentIntegrityEnd = position;
269 skipUntil<UChar, isASCIISpace>(currentIntegrityEnd, end); 270 skipUntil<UChar, isASCIISpace>(currentIntegrityEnd, end);
270 271
271 // Algorithm parsing errors are non-fatal (the subresource should 272 // Algorithm parsing errors are non-fatal (the subresource should
272 // still be loaded) because strong hash algorithms should be used 273 // still be loaded) because strong hash algorithms should be used
273 // without fear of breaking older user agents that don't support 274 // without fear of breaking older user agents that don't support
274 // them. 275 // them.
275 AlgorithmParseResult parseResult = parseAlgorithm(position, currentInteg rityEnd, algorithm); 276 AlgorithmParseResult parseResult = parseAlgorithm(position, currentInteg rityEnd, algorithm);
(...skipping 17 matching lines...) Expand all
293 ASSERT(parseResult == AlgorithmValid); 294 ASSERT(parseResult == AlgorithmValid);
294 295
295 if (!parseDigest(position, currentIntegrityEnd, digest)) { 296 if (!parseDigest(position, currentIntegrityEnd, digest)) {
296 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribu te + "'). The digest must be a valid, base64-encoded value.", document); 297 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribu te + "'). The digest must be a valid, base64-encoded value.", document);
297 error = true; 298 error = true;
298 skipUntil<UChar, isASCIISpace>(position, end); 299 skipUntil<UChar, isASCIISpace>(position, end);
299 UseCounter::count(document, UseCounter::SRIElementWithUnparsableInte grityAttribute); 300 UseCounter::count(document, UseCounter::SRIElementWithUnparsableInte grityAttribute);
300 continue; 301 continue;
301 } 302 }
302 303
303 if (!parseMimeType(position, currentIntegrityEnd, type)) { 304 bool unparsableOptions = false;
304 logErrorToConsole("Error parsing 'integrity' attribute ('" + attribu te + "'). The content type could not be parsed.", document); 305 while (*position == '?') {
305 error = true; 306 String name, value;
306 skipUntil<UChar, isASCIISpace>(position, end); 307 if (!parseOption(position, currentIntegrityEnd, name, value)) {
307 UseCounter::count(document, UseCounter::SRIElementWithUnparsableInte grityAttribute); 308 logErrorToConsole("Error parsing 'integrity' attribute ('" + att ribute + "'). The options could not be parsed.", document);
308 continue; 309 unparsableOptions = true;
310 error = true;
311 skipUntil<UChar, isASCIISpace>(position, end);
312 UseCounter::count(document, UseCounter::SRIElementWithUnparsable IntegrityAttribute);
313 break;
314 }
315
316 logErrorToConsole("Ignoring unrecogized 'integrity' attribute option '" + name + "' with value '" + value + "'.", document);
Mike West 2015/05/11 03:25:29 This would end up logging a lot if/when options be
jww 2015/05/11 05:39:07 My new change logs just once the entire unknown op
309 } 317 }
310 318
311 IntegrityMetadata integrityMetadata = { 319 if (!unparsableOptions) {
Mike West 2015/05/11 03:25:29 Why do you need a separate boolean here? Would che
jww 2015/05/11 05:39:07 Alas, it wouldn't have been :-( Error tracks if an
312 digest, 320 IntegrityMetadata integrityMetadata = {
313 algorithm, 321 digest,
314 type 322 algorithm
315 }; 323 };
316 metadataList.append(integrityMetadata); 324 metadataList.append(integrityMetadata);
325 }
317 } 326 }
318 327
319 if (metadataList.size() == 0 && error) 328 if (metadataList.size() == 0 && error)
320 return IntegrityParseNoValidResult; 329 return IntegrityParseNoValidResult;
321 330
322 return IntegrityParseValidResult; 331 return IntegrityParseValidResult;
323 } 332 }
324 333
325 } // namespace blink 334 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698