Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /*
2 * Tests bugs 597706, 655389: prevent duplicate headers with differing values
3 * for some headers like Content-Length, Location, etc.
4 */
6 ////////////////////////////////////////////////////////////////////////////////
7 // Test infrastructure
9 Cu.import("resource://testing-common/httpd.js");
11 XPCOMUtils.defineLazyGetter(this, "URL", function() {
12 return "http://localhost:" + httpserver.identity.primaryPort;
13 });
15 var httpserver = new HttpServer();
16 var index = 0;
17 var test_flags = new Array();
18 var testPathBase = "/dupe_hdrs";
20 function run_test()
21 {
22 httpserver.start(-1);
24 do_test_pending();
25 run_test_number(1);
26 }
28 function run_test_number(num)
29 {
30 testPath = testPathBase + num;
31 httpserver.registerPathHandler(testPath, eval("handler" + num));
33 var channel = setupChannel(testPath);
34 flags = test_flags[num]; // OK if flags undefined for test
35 channel.asyncOpen(new ChannelListener(eval("completeTest" + num),
36 channel, flags), null);
37 }
39 function setupChannel(url)
40 {
41 var ios = Components.classes["@mozilla.org/network/io-service;1"].
42 getService(Ci.nsIIOService);
43 var chan = ios.newChannel(URL + url, "", null);
44 var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
45 return httpChan;
46 }
48 function endTests()
49 {
50 httpserver.stop(do_test_finished);
51 }
53 ////////////////////////////////////////////////////////////////////////////////
54 // Test 1: FAIL because of conflicting Content-Length headers
55 test_flags[1] = CL_EXPECT_FAILURE;
57 function handler1(metadata, response)
58 {
59 var body = "012345678901234567890123456789";
60 // Comrades! We must seize power from the petty-bourgeois running dogs of
61 // httpd.js in order to reply with multiple instances of the same header!
62 response.seizePower();
63 response.write("HTTP/1.0 200 OK\r\n");
64 response.write("Content-Type: text/plain\r\n");
65 response.write("Content-Length: 30\r\n");
66 response.write("Content-Length: 20\r\n");
67 response.write("\r\n");
68 response.write(body);
69 response.finish();
70 }
73 function completeTest1(request, data, ctx)
74 {
75 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
77 run_test_number(2);
78 }
80 ////////////////////////////////////////////////////////////////////////////////
81 // Test 2: OK to have duplicate same Content-Length headers
83 function handler2(metadata, response)
84 {
85 var body = "012345678901234567890123456789";
86 response.seizePower();
87 response.write("HTTP/1.0 200 OK\r\n");
88 response.write("Content-Type: text/plain\r\n");
89 response.write("Content-Length: 30\r\n");
90 response.write("Content-Length: 30\r\n");
91 response.write("\r\n");
92 response.write(body);
93 response.finish();
94 }
96 function completeTest2(request, data, ctx)
97 {
98 do_check_eq(request.status, 0);
99 run_test_number(3);
100 }
102 ////////////////////////////////////////////////////////////////////////////////
103 // Test 3: FAIL: 2nd Content-length is blank
104 test_flags[3] = CL_EXPECT_FAILURE;
106 function handler3(metadata, response)
107 {
108 var body = "012345678901234567890123456789";
109 response.seizePower();
110 response.write("HTTP/1.0 200 OK\r\n");
111 response.write("Content-Type: text/plain\r\n");
112 response.write("Content-Length: 30\r\n");
113 response.write("Content-Length:\r\n");
114 response.write("\r\n");
115 response.write(body);
116 response.finish();
117 }
119 function completeTest3(request, data, ctx)
120 {
121 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
123 run_test_number(4);
124 }
126 ////////////////////////////////////////////////////////////////////////////////
127 // Test 4: ensure that blank C-len header doesn't allow attacker to reset Clen,
128 // then insert CRLF attack
129 test_flags[4] = CL_EXPECT_FAILURE;
131 function handler4(metadata, response)
132 {
133 var body = "012345678901234567890123456789";
135 response.seizePower();
136 response.write("HTTP/1.0 200 OK\r\n");
137 response.write("Content-Type: text/plain\r\n");
138 response.write("Content-Length: 30\r\n");
140 // Bad Mr Hacker! Bad!
141 var evilBody = "We are the Evil bytes, Evil bytes, Evil bytes!";
142 response.write("Content-Length:\r\n");
143 response.write("Content-Length: %s\r\n\r\n%s" % (evilBody.length, evilBody));
144 response.write("\r\n");
145 response.write(body);
146 response.finish();
147 }
149 function completeTest4(request, data, ctx)
150 {
151 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
153 run_test_number(5);
154 }
157 ////////////////////////////////////////////////////////////////////////////////
158 // Test 5: ensure that we take 1st instance of duplicate, nonmerged headers that
159 // are permitted : (ex: Referrer)
161 function handler5(metadata, response)
162 {
163 var body = "012345678901234567890123456789";
164 response.seizePower();
165 response.write("HTTP/1.0 200 OK\r\n");
166 response.write("Content-Type: text/plain\r\n");
167 response.write("Content-Length: 30\r\n");
168 response.write("Referer: naive.org\r\n");
169 response.write("Referer: evil.net\r\n");
170 response.write("\r\n");
171 response.write(body);
172 response.finish();
173 }
175 function completeTest5(request, data, ctx)
176 {
177 try {
178 referer = request.getResponseHeader("Referer");
179 do_check_eq(referer, "naive.org");
180 } catch (ex) {
181 do_throw("Referer header should be present");
182 }
184 run_test_number(6);
185 }
187 ////////////////////////////////////////////////////////////////////////////////
188 // Test 5: FAIL if multiple, different Location: headers present
189 // - needed to prevent CRLF injection attacks
190 test_flags[6] = CL_EXPECT_FAILURE;
192 function handler6(metadata, response)
193 {
194 var body = "012345678901234567890123456789";
195 response.seizePower();
196 response.write("HTTP/1.0 301 Moved\r\n");
197 response.write("Content-Type: text/plain\r\n");
198 response.write("Content-Length: 30\r\n");
199 response.write("Location: " + URL + "/content\r\n");
200 response.write("Location: http://www.microsoft.com/\r\n");
201 response.write("Connection: close\r\n");
202 response.write("\r\n");
203 response.write(body);
204 response.finish();
205 }
207 function completeTest6(request, data, ctx)
208 {
209 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
211 // run_test_number(7); // Test 7 leaking under e10s: unrelated bug?
212 run_test_number(8);
213 }
215 ////////////////////////////////////////////////////////////////////////////////
216 // Test 7: OK to have multiple Location: headers with same value
218 function handler7(metadata, response)
219 {
220 var body = "012345678901234567890123456789";
221 response.seizePower();
222 response.write("HTTP/1.0 301 Moved\r\n");
223 response.write("Content-Type: text/plain\r\n");
224 response.write("Content-Length: 30\r\n");
225 // redirect to previous test handler that completes OK: test 5
226 response.write("Location: " + URL + testPathBase + "5\r\n");
227 response.write("Location: " + URL + testPathBase + "5\r\n");
228 response.write("Connection: close\r\n");
229 response.write("\r\n");
230 response.write(body);
231 response.finish();
232 }
234 function completeTest7(request, data, ctx)
235 {
236 // for some reason need this here
237 request.QueryInterface(Components.interfaces.nsIHttpChannel);
239 try {
240 referer = request.getResponseHeader("Referer");
241 do_check_eq(referer, "naive.org");
242 } catch (ex) {
243 do_throw("Referer header should be present");
244 }
246 run_test_number(8);
247 }
249 ////////////////////////////////////////////////////////////////////////////////
250 // FAIL if 2nd Location: headers blank
251 test_flags[8] = CL_EXPECT_FAILURE;
253 function handler8(metadata, response)
254 {
255 var body = "012345678901234567890123456789";
256 response.seizePower();
257 response.write("HTTP/1.0 301 Moved\r\n");
258 response.write("Content-Type: text/plain\r\n");
259 response.write("Content-Length: 30\r\n");
260 // redirect to previous test handler that completes OK: test 4
261 response.write("Location: " + URL + testPathBase + "4\r\n");
262 response.write("Location:\r\n");
263 response.write("Connection: close\r\n");
264 response.write("\r\n");
265 response.write(body);
266 response.finish();
267 }
269 function completeTest8(request, data, ctx)
270 {
271 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
273 run_test_number(9);
274 }
276 ////////////////////////////////////////////////////////////////////////////////
277 // Test 9: ensure that blank Location header doesn't allow attacker to reset,
278 // then insert an evil one
279 test_flags[9] = CL_EXPECT_FAILURE;
281 function handler9(metadata, response)
282 {
283 var body = "012345678901234567890123456789";
284 response.seizePower();
285 response.write("HTTP/1.0 301 Moved\r\n");
286 response.write("Content-Type: text/plain\r\n");
287 response.write("Content-Length: 30\r\n");
288 // redirect to previous test handler that completes OK: test 2
289 response.write("Location: " + URL + testPathBase + "2\r\n");
290 response.write("Location:\r\n");
291 // redirect to previous test handler that completes OK: test 4
292 response.write("Location: " + URL + testPathBase + "4\r\n");
293 response.write("Connection: close\r\n");
294 response.write("\r\n");
295 response.write(body);
296 response.finish();
297 }
299 function completeTest9(request, data, ctx)
300 {
301 // All redirection should fail:
302 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
304 run_test_number(10);
305 }
307 ////////////////////////////////////////////////////////////////////////////////
308 // Test 10: FAIL: if conflicting values for Content-Dispo
309 test_flags[10] = CL_EXPECT_FAILURE;
311 function handler10(metadata, response)
312 {
313 var body = "012345678901234567890123456789";
314 response.seizePower();
315 response.write("HTTP/1.0 200 OK\r\n");
316 response.write("Content-Type: text/plain\r\n");
317 response.write("Content-Length: 30\r\n");
318 response.write("Content-Disposition: attachment; filename=foo\r\n");
319 response.write("Content-Disposition: attachment; filename=bar\r\n");
320 response.write("Content-Disposition: attachment; filename=baz\r\n");
321 response.write("\r\n");
322 response.write(body);
323 response.finish();
324 }
327 function completeTest10(request, data, ctx)
328 {
329 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
331 run_test_number(11);
332 }
334 ////////////////////////////////////////////////////////////////////////////////
335 // Test 11: OK to have duplicate same Content-Disposition headers
337 function handler11(metadata, response)
338 {
339 var body = "012345678901234567890123456789";
340 response.seizePower();
341 response.write("HTTP/1.0 200 OK\r\n");
342 response.write("Content-Type: text/plain\r\n");
343 response.write("Content-Length: 30\r\n");
344 response.write("Content-Disposition: attachment; filename=foo\r\n");
345 response.write("Content-Disposition: attachment; filename=foo\r\n");
346 response.write("\r\n");
347 response.write(body);
348 response.finish();
349 }
351 function completeTest11(request, data, ctx)
352 {
353 do_check_eq(request.status, 0);
355 try {
356 var chan = request.QueryInterface(Ci.nsIChannel);
357 do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
358 do_check_eq(chan.contentDispositionFilename, "foo");
359 do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo");
360 } catch (ex) {
361 do_throw("error parsing Content-Disposition: " + ex);
362 }
364 run_test_number(12);
365 }
367 ////////////////////////////////////////////////////////////////////////////////
368 // Bug 716801 OK for Location: header to be blank
370 function handler12(metadata, response)
371 {
372 var body = "012345678901234567890123456789";
373 response.seizePower();
374 response.write("HTTP/1.0 200 OK\r\n");
375 response.write("Content-Type: text/plain\r\n");
376 response.write("Content-Length: 30\r\n");
377 response.write("Location:\r\n");
378 response.write("Connection: close\r\n");
379 response.write("\r\n");
380 response.write(body);
381 response.finish();
382 }
384 function completeTest12(request, data, ctx)
385 {
386 do_check_eq(request.status, Components.results.NS_OK);
387 do_check_eq(30, data.length);
389 run_test_number(13);
390 }
392 ////////////////////////////////////////////////////////////////////////////////
393 // Negative content length is ok
394 test_flags[13] = CL_ALLOW_UNKNOWN_CL;
396 function handler13(metadata, response)
397 {
398 var body = "012345678901234567890123456789";
399 response.seizePower();
400 response.write("HTTP/1.0 200 OK\r\n");
401 response.write("Content-Type: text/plain\r\n");
402 response.write("Content-Length: -1\r\n");
403 response.write("Connection: close\r\n");
404 response.write("\r\n");
405 response.write(body);
406 response.finish();
407 }
409 function completeTest13(request, data, ctx)
410 {
411 do_check_eq(request.status, Components.results.NS_OK);
412 do_check_eq(30, data.length);
414 run_test_number(14);
415 }
417 ////////////////////////////////////////////////////////////////////////////////
418 // leading negative content length is not ok if paired with positive one
420 test_flags[14] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
422 function handler14(metadata, response)
423 {
424 var body = "012345678901234567890123456789";
425 response.seizePower();
426 response.write("HTTP/1.0 200 OK\r\n");
427 response.write("Content-Type: text/plain\r\n");
428 response.write("Content-Length: -1\r\n");
429 response.write("Content-Length: 30\r\n");
430 response.write("Connection: close\r\n");
431 response.write("\r\n");
432 response.write(body);
433 response.finish();
434 }
436 function completeTest14(request, data, ctx)
437 {
438 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
440 run_test_number(15);
441 }
443 ////////////////////////////////////////////////////////////////////////////////
444 // trailing negative content length is not ok if paired with positive one
446 test_flags[15] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
448 function handler15(metadata, response)
449 {
450 var body = "012345678901234567890123456789";
451 response.seizePower();
452 response.write("HTTP/1.0 200 OK\r\n");
453 response.write("Content-Type: text/plain\r\n");
454 response.write("Content-Length: 30\r\n");
455 response.write("Content-Length: -1\r\n");
456 response.write("Connection: close\r\n");
457 response.write("\r\n");
458 response.write(body);
459 response.finish();
460 }
462 function completeTest15(request, data, ctx)
463 {
464 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
466 run_test_number(16);
467 }
469 ////////////////////////////////////////////////////////////////////////////////
470 // empty content length is ok
471 test_flags[16] = CL_ALLOW_UNKNOWN_CL;
472 reran16 = false;
474 function handler16(metadata, response)
475 {
476 var body = "012345678901234567890123456789";
477 response.seizePower();
478 response.write("HTTP/1.0 200 OK\r\n");
479 response.write("Content-Type: text/plain\r\n");
480 response.write("Content-Length: \r\n");
481 response.write("Cache-Control: max-age=600\r\n");
482 response.write("Connection: close\r\n");
483 response.write("\r\n");
484 response.write(body);
485 response.finish();
486 }
488 function completeTest16(request, data, ctx)
489 {
490 do_check_eq(request.status, Components.results.NS_OK);
491 do_check_eq(30, data.length);
493 if (!reran16) {
494 reran16 = true;
495 run_test_number(16);
496 }
497 else {
498 run_test_number(17);
499 }
500 }
502 ////////////////////////////////////////////////////////////////////////////////
503 // empty content length paired with non empty is not ok
504 test_flags[17] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
506 function handler17(metadata, response)
507 {
508 var body = "012345678901234567890123456789";
509 response.seizePower();
510 response.write("HTTP/1.0 200 OK\r\n");
511 response.write("Content-Type: text/plain\r\n");
512 response.write("Content-Length: \r\n");
513 response.write("Content-Length: 30\r\n");
514 response.write("Connection: close\r\n");
515 response.write("\r\n");
516 response.write(body);
517 response.finish();
518 }
520 function completeTest17(request, data, ctx)
521 {
522 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
524 run_test_number(18);
525 }
527 ////////////////////////////////////////////////////////////////////////////////
528 // alpha content-length is just like -1
529 test_flags[18] = CL_ALLOW_UNKNOWN_CL;
531 function handler18(metadata, response)
532 {
533 var body = "012345678901234567890123456789";
534 response.seizePower();
535 response.write("HTTP/1.0 200 OK\r\n");
536 response.write("Content-Type: text/plain\r\n");
537 response.write("Content-Length: seventeen\r\n");
538 response.write("Connection: close\r\n");
539 response.write("\r\n");
540 response.write(body);
541 response.finish();
542 }
544 function completeTest18(request, data, ctx)
545 {
546 do_check_eq(request.status, Components.results.NS_OK);
547 do_check_eq(30, data.length);
549 run_test_number(19);
550 }
552 ////////////////////////////////////////////////////////////////////////////////
553 // semi-colons are ok too in the content-length
554 test_flags[19] = CL_ALLOW_UNKNOWN_CL;
556 function handler19(metadata, response)
557 {
558 var body = "012345678901234567890123456789";
559 response.seizePower();
560 response.write("HTTP/1.0 200 OK\r\n");
561 response.write("Content-Type: text/plain\r\n");
562 response.write("Content-Length: 30;\r\n");
563 response.write("Connection: close\r\n");
564 response.write("\r\n");
565 response.write(body);
566 response.finish();
567 }
569 function completeTest19(request, data, ctx)
570 {
571 do_check_eq(request.status, Components.results.NS_OK);
572 do_check_eq(30, data.length);
574 run_test_number(20);
575 }
577 ////////////////////////////////////////////////////////////////////////////////
578 // FAIL if 1st Location: header is blank, followed by non-blank
579 test_flags[20] = CL_EXPECT_FAILURE;
581 function handler20(metadata, response)
582 {
583 var body = "012345678901234567890123456789";
584 response.seizePower();
585 response.write("HTTP/1.0 301 Moved\r\n");
586 response.write("Content-Type: text/plain\r\n");
587 response.write("Content-Length: 30\r\n");
588 // redirect to previous test handler that completes OK: test 4
589 response.write("Location:\r\n");
590 response.write("Location: " + URL + testPathBase + "4\r\n");
591 response.write("Connection: close\r\n");
592 response.write("\r\n");
593 response.write(body);
594 response.finish();
595 }
597 function completeTest20(request, data, ctx)
598 {
599 do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
601 endTests();
602 }