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

Side by Side Diff: tools/chrome_proxy/webdriver/protocol_fuzz.py

Issue 2928863002: Add client protocol fuzzer test (Closed)
Patch Set: tombergan comments Created 3 years, 6 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright 2017 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4 #
5 #
6 # This test checks that Chrome does not crash or otherwise wildly misbehave when
7 # it receives unexpected Chrome-Proxy header values and/or directives. This is
8 # done by fuzz testing which is configured through a dictionary object at the
9 # top of this file.
10 #
11 # The fuzz testing generates URLs which are requested from the Chrome Proxy test
12 # server which will modify its response based on the encoded data in the URL.
13 #
14 # The fuzz testing is configured in a dictionary-type object of the following
15 # format:
16 # {
17 # # This specifies a header that will be fuzzed. Only one header is fuzzed
18 # # per test. When a header is being fuzz tested, any entry it has in the
19 # # STATIC_RESPONSE_HEADERS object will be overwritten.
20 # "chrome-proxy": {
21 # # This specifies the directive key values that are possible to use in
22 # # fuzzing.
23 # "directive_keys: [
24 # "page-policy",
25 # "foo",
26 # ],
27 # # This specifies the directive values that are possible to use in
28 # # fuzzing. Any one of these values may end up mapped to any one of the
29 # # directive_keys above.
30 # "directive_values": [
31 # "empty-image",
32 # "bar",
33 # ],
34 # # The maximum number of directives to use in this header. If this value
35 # # is greater than the number of directives given above, that will be
36 # # used instead.
37 # "max_directives": 10,
38 #
39 # # The maximum number of directive values to use per directive. If this
40 # # value is greater than the number of directive values given above, that
41 # # will be used instead.
42 # "max_directive_values": 10,
43 #
44 # # This is used to join the directive values when multiple are used.
45 # "directive_value_joiner": "|",
46 # },
47 # }
48 #
49 # If the above configuration was given, the following values would be generated
50 # for the chrome-proxy header:
51 # <empty-string>
52 # foo
53 # foo=bar
54 # foo=empty-image
55 # foo=bar|empty-image
56 # page_policies
57 # page_policies=bar
58 # page_policies=empty-image
59 # page_policies=bar|empty-image
60 # foo,page_policies
61 # foo=bar,page_policies=bar
62 # foo=empty-image,page_policies=empty-image
63 # foo=bar|empty-image,page_policies=bar|empty-image
64 #
65 # Randomly generated values are also supported in the fuzz_header field
66 # ("chrome-proxy" in the above example), directive_keys, and directive_values.
67 # Randomly generated values can be specified using these formats:
68 # {{RAND_STR(N)}}
69 # Creates a random string of lowercase and digit characters of length N
70 #
71 # {{RAND_INT(N)}}
72 # Creates a random integer in the range [0, 10^N+1) with leading zeros
73 # Example: "Num cookies: {{RAND_INT(4)}}" yields "Num cookies: 0123"
74 #
75 # {{RAND_DBL(P.Q)}}
76 # Creates a random double in the range [0, 10^P+1) with leading zeros and
77 # up to Q places after the decimal point
78 # Example: "My money = ${{RAND_DBL(3,2)}}" yields "My money = $001.53"
79 #
80 # {{RAND_BOOL}}
81 # Creates a random boolean, either 'true' or 'false'
82 # Example: "I am awesome: {{RAND_BOOL}}" yields "I am awesome: true"
83
84 import BaseHTTPServer
85 import base64
86 import itertools
87 import json
88 import random
89 import re
90 import string
91
92 from common import TestDriver
93 from common import IntegrationTest
94 from decorators import Slow
95
96 # This dict configures how the fuzzing will operate. See documentation above for
97 # more information.
98 FUZZ_HEADERS = {
99 "chrome-proxy": {
100 "directive_keys": [
101 "{{RAND_STR(10)}}",
102 "{{RAND_STR(10)}}",
Tom Bergan 2017/06/08 15:00:18 Do we actually need two of these?
Robert Ogden 2017/06/08 17:17:58 I was thinking yes so that we get three directives
103 "page-policies",
104 ],
105 "directive_values": [
106 "empty-image",
107 "{{RAND_INT(1)}}",
108 "{{RAND_INT(4)}}",
109 "{{RAND_STR(10)}}",
110 "{{RAND_STR(10)}}",
111 "{{RAND_STR(10)}}",
Tom Bergan 2017/06/08 15:00:18 Ditto here
Robert Ogden 2017/06/08 17:17:58 Yes, so that there are three random strings in at
112 ],
113 "max_directives": 3,
114 "max_directive_values": 3,
115 "directive_value_joiner": "|",
116 },
117 }
118
119 TEST_SERVER = "chromeproxy-test.appspot.com"
120
121 # These headers will be present in every test server response. If one of these
122 # entries is also a fuzzed header above, then the fuzzed value will take the
123 # place of the static one instead.
124 STATIC_RESPONSE_HEADERS = {
125 "content-type": ["text/html"],
126 "via": ["1.1 Chrome-Compression-Proxy"],
127 "cache-control": ["no-cache, no-store, must-revalidate"],
128 "pragma": ["no-cache"],
129 "expires": ["0"],
130 }
131
132 # This string will be used as the response body in every test and will be
133 # checked for existance on the final loaded page.
134 STATIC_RESPONSE_BODY = 'ok'
135
136 rand_str_re = re.compile(r'{{RAND_STR\((\d+)\)}}')
137 rand_int_re = re.compile(r'{{RAND_INT\((\d+)\)}}')
138 rand_dbl_re = re.compile(r'{{RAND_DBL\((\d+)\.(\d+)\)}}')
139 rand_bool_re = re.compile(r'{{RAND_BOOL}}')
140
141 def ParseRand(key, val):
142 """This helper function parses the {{RAND}} expressions in the given values
143 and returns them with random values subsituted in place.
144
145 Args:
146 key: the header key with 0 or more {{RAND}} expressions
147 val: the header value with 0 or more {{RAND}} expressions
148 Returns:
149 A key, value tuple with subsituted random values
150 """
151 def GenerateRand(length, charset):
152 return ''.join(random.choice(charset) for _ in range(length))
153 def _parse_rand(v):
154 result = v
155 had_match = True
156 while had_match:
157 had_match = False
158 str_match = rand_str_re.search(result)
159 if str_match:
160 had_match = True
161 mag = int(result[str_match.start(1):str_match.end(1)])
162 rand_str = GenerateRand(mag, string.ascii_lowercase + string.digits)
163 result = (result[:str_match.start()] + rand_str
164 + result[str_match.end():])
165 int_match = rand_int_re.search(result)
166 if int_match:
167 had_match = True
168 mag = int(result[int_match.start(1):int_match.end(1)])
169 rand_int = GenerateRand(mag, string.digits)
170 result = (result[:int_match.start()] + rand_int
171 + result[int_match.end():])
172 dbl_match = rand_dbl_re.search(result)
173 if dbl_match:
174 had_match = True
175 magN = int(result[dbl_match.start(1):dbl_match.end(1)])
176 magD = int(result[dbl_match.start(2):dbl_match.end(2)])
177 rand_dbl = GenerateRand(magN, string.digits) + '.' + GenerateRand(magD,
178 string.digits)
179 result = (result[:dbl_match.start()] + rand_dbl
180 + result[dbl_match.end():])
181 bool_match = rand_bool_re.search(result)
182 if bool_match:
183 had_match = True
184 rand_bool = bool(random.getrandbits(1))
185 result = (result[:bool_match.start()] + str(rand_bool).lower()
186 + result[bool_match.end():])
187 return result
188 return (_parse_rand(key), _parse_rand(val))
189
190 def GenerateFuzzedHeaders(cfg=FUZZ_HEADERS):
191 """This function yields header key value pairs which can be used to update a
192 Python dict representing HTTP headers. See file level documentation for more
193 information.
194
195 Args:
196 cfg: the configuration dict that specifies how to fuzz the proxy headers
197 Yields:
198 one header key value pair
199 """
200 for header_key in cfg:
201 fuzz = cfg[header_key]
202 dirs = fuzz['directive_keys']
203 vals = fuzz['directive_values']
204 max_dirs = min(fuzz['max_directives'], len(dirs))
205 max_vals = min(fuzz['max_directive_values'], len(vals))
206 def GenerateFuzzedValues():
207 for n in range(0, max_vals + 1):
208 for c in itertools.combinations(vals, n):
209 yield c
210 # Yield an empty header key,value pair before doing all the combinations.
211 yield (header_key, '')
212 for num_dirs in range(1, max_dirs + 1):
213 for directive_set in itertools.combinations(dirs, num_dirs):
214 for values in GenerateFuzzedValues():
215 value_list = list(values)
216 if '' in value_list:
217 value_list.remove('')
218 value_str = fuzz['directive_value_joiner'].join(value_list)
219 header = []
220 for directive in directive_set:
221 if len(value_str) == 0:
222 header.append(directive)
223 else:
224 header.append('%s=%s' % (directive, value_str))
225 yield ParseRand(header_key, ','.join(header))
226
227 class FuzzUnitTests(IntegrationTest):
228
229 def testParseRand(self):
230 tests = {
231 "{{RAND_STR(1)}": r"{{RAND_STR\(1\)}",
232 "{{RAND_INT(1)}": r"{{RAND_INT\(1\)}",
233 "{{RAND_DBL(1.1)}": r"{{RAND_DBL\(1\.1\)}",
234 "{{RAND_DBL(11)}}": r"{{RAND_DBL\(11\)}}",
235 "{{RAND_BOOL}": r"{{RAND_BOOL}",
236 "{{RAND_STR(0)}}": "",
237 "hi{{RAND_STR(0)}}": "hi",
238 "{{RAND_STR(0)}}there": "there",
239 "hi{{RAND_STR(0)}}there": "hithere",
240 "{{RAND_STR(3)}}": r"[a-z0-9][a-z0-9][a-z0-9]",
241 "{{RAND_STR(3)}}there": r"[a-z0-9][a-z0-9][a-z0-9]there",
242 "hi{{RAND_STR(3)}}": r"hi[a-z0-9][a-z0-9][a-z0-9]",
243 "hi{{RAND_STR(3)}}there": r"hi[a-z0-9][a-z0-9][a-z0-9]there",
244 "{{RAND_INT(0)}}": "",
245 "hi{{RAND_INT(0)}}": "hi",
246 "{{RAND_INT(0)}}there": "there",
247 "hi{{RAND_INT(0)}}there": "hithere",
248 "{{RAND_INT(3)}}": r"[0-9][0-9][0-9]",
249 "{{RAND_INT(3)}}there": r"[0-9][0-9][0-9]there",
250 "hi{{RAND_INT(3)}}": r"hi[0-9][0-9][0-9]",
251 "hi{{RAND_INT(3)}}there": r"hi[0-9][0-9][0-9]there",
252 "{{RAND_DBL(0.0)}}": r"\.",
253 "hi{{RAND_DBL(0.0)}}": r"hi\.",
254 "{{RAND_DBL(0.0)}}there": r"\.there",
255 "hi{{RAND_DBL(0.0)}}there": r"hi\.there",
256 "{{RAND_DBL(3.3)}}": r"[0-9][0-9][0-9]\.[0-9][0-9][0-9]",
257 "hi{{RAND_DBL(3.3)}}": r"hi[0-9][0-9][0-9]\.[0-9][0-9][0-9]",
258 "{{RAND_DBL(3.3)}}there": r"[0-9][0-9][0-9]\.[0-9][0-9][0-9]there",
259 "hi{{RAND_DBL(3.3)}}there": r"hi[0-9][0-9][0-9]\.[0-9][0-9][0-9]there",
260 "{{RAND_BOOL}}": r"(true|false)",
261 "{{RAND_BOOL}}there": r"(true|false)there",
262 "hi{{RAND_BOOL}}": r"hi(true|false)",
263 "hi{{RAND_BOOL}}there": r"hi(true|false)there",
264 "{{RAND_STR(1)}}{{RAND_STR(1)}}": r"[a-z0-9][a-z0-9]",
265 "{{RAND_STR(1)}}{{RAND_INT(1)}}": r"[a-z0-9][0-9]",
266 "{{RAND_STR(1)}}{{RAND_DBL(1.1)}}": r"[a-z0-9][0-9]\.[0-9]",
267 "{{RAND_STR(1)}}{{RAND_BOOL}}": r"[a-z0-9](true|false)",
268 "{{RAND_INT(1)}}{{RAND_STR(1)}}": r"[0-9][a-z0-9]",
269 "{{RAND_INT(1)}}{{RAND_INT(1)}}": r"[0-9][0-9]",
270 "{{RAND_INT(1)}}{{RAND_DBL(1.1)}}": r"[0-9][0-9]\.[0-9]",
271 "{{RAND_INT(1)}}{{RAND_BOOL}}": r"[0-9](true|false)",
272 "{{RAND_DBL(1.1)}}{{RAND_STR(1)}}": r"[0-9]\.[0-9][a-z0-9]",
273 "{{RAND_DBL(1.1)}}{{RAND_INT(1)}}": r"[0-9]\.[0-9][0-9]",
274 "{{RAND_DBL(1.1)}}{{RAND_DBL(1.1)}}": r"[0-9]\.[0-9][0-9]\.[0-9]",
275 "{{RAND_DBL(1.1)}}{{RAND_BOOL}}": r"[0-9]\.[0-9](true|false)",
276 "{{RAND_BOOL}}{{RAND_STR(1)}}": r"(true|false)[a-z0-9]",
277 "{{RAND_BOOL}}{{RAND_INT(1)}}": r"(true|false)[0-9]",
278 "{{RAND_BOOL}}{{RAND_DBL(1.1)}}": r"(true|false)[0-9]\.[0-9]",
279 "{{RAND_BOOL}}{{RAND_BOOL}}": r"(true|false)(true|false)",
280 }
281 for t in tests:
282 expected = re.compile('^' + tests[t] + '$')
283 gotK, gotV = ParseRand(t, t)
284 if not expected.match(gotK):
285 self.fail("%s doesn't match /%s/" % (gotK, tests[t]))
286 if not expected.match(gotV):
287 self.fail("%s doesn't match /%s/" % (gotK, tests[t]))
288
289 def testGenerator(self):
290 test_cfg = {
291 "chrome-proxy": {
292 "directive_keys": [
293 "foo",
294 "page_policies",
295 ],
296 "directive_values": [
297 "bar",
298 "empty-image",
299 ],
300 "max_directives": 10,
301 "max_directive_values": 10,
302 "directive_value_joiner": "|",
303 },
304 }
305 expected_headers = ['', 'foo', 'foo=bar', 'foo=empty-image',
306 'foo=bar|empty-image', 'page_policies', 'page_policies=bar',
307 'page_policies=empty-image', 'page_policies=bar|empty-image',
308 'foo,page_policies', 'foo=bar,page_policies=bar',
309 'foo=empty-image,page_policies=empty-image',
310 'foo=bar|empty-image,page_policies=bar|empty-image',
311 ]
312 actual_headers = []
313 for h in GenerateFuzzedHeaders(cfg=test_cfg):
314 actual_headers.append(h[1])
315 expected_headers.sort()
316 actual_headers.sort()
317 self.assertEqual(expected_headers, actual_headers)
318
319 class ProtocolFuzzer(IntegrationTest):
320
321 def GenerateTestURLs(self):
322 """This function yields test URLs which will cause the test server to
323 respond with the given given headers and body.
324
325 Yields:
326 URLs suitable for testing fuzzed response headers
327 """
328 for fz_key, fz_val in GenerateFuzzedHeaders():
329 headers = {}
330 headers.update(STATIC_RESPONSE_HEADERS)
331 headers.update({fz_key: [fz_val]})
332 json_headers = json.dumps(headers)
333 b64_headers = base64.b64encode(json_headers)
334 url = "http://%s/default?respBody=%s&respHeader=%s" % (TEST_SERVER,
335 base64.b64encode(STATIC_RESPONSE_BODY), b64_headers)
336 yield (json_headers, url)
337
338 @Slow
339 def testFuzzing(self):
340 with TestDriver() as t:
341 t.AddChromeArg('--enable-spdy-proxy-auth')
342 t.AddChromeArg('--data-reduction-proxy-http-proxies='
343 'https://chromeproxy-test.appspot.com')
344 for headers, url in self.GenerateTestURLs():
345 try:
346 t.LoadURL(url)
347 # The main test is to make sure Chrome doesn't crash after loading a
348 # page with fuzzed headers, which would be raised as a ChromeDriver
349 # exception. Otherwise, we'll do a simple check and make sure the page
350 # body is correct and Chrome isn't displaying some kind of error page.
351 body = t.ExecuteJavascriptStatement('document.body.innerHTML')
352 self.assertEqual(body, STATIC_RESPONSE_BODY)
353 except Exception as e:
354 print 'Response headers: ' + headers
355 print 'URL: ' + url
356 raise e
357
358 if __name__ == '__main__':
359 IntegrationTest.RunAllTests()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698