1 // Copyright (c) 2011, Mike Samuel 2 // All rights reserved. 3 // 4 // Redistribution and use in source and binary forms, with or without 5 // modification, are permitted provided that the following conditions 6 // are met: 7 // 8 // Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // Neither the name of the OWASP nor the names of its contributors may 14 // be used to endorse or promote products derived from this software 15 // without specific prior written permission. 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 19 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 20 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 21 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 26 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 // POSSIBILITY OF SUCH DAMAGE. 28 29 package org.owasp.html; 30 31 import java.util.List; 32 import java.util.concurrent.LinkedBlockingQueue; 33 import java.util.concurrent.ThreadPoolExecutor; 34 import java.util.concurrent.TimeUnit; 35 36 import com.google.common.base.Charsets; 37 import com.google.common.base.Throwables; 38 import com.google.common.io.Resources; 39 40 /** 41 * Throws malformed inputs at the HTML sanitizer to try and crash it. 42 * This test is stochastic -- not guaranteed to pass or fail consistently. 43 * If you see a failure, please report it along with the seed from the output. 44 * If you want to repeat a failure, set the system property "junit.seed". 45 * 46 * @author Mike Samuel <mikesamuel@gmail.com> 47 */ 48 public class HtmlSanitizerFuzzerTest extends FuzzyTestCase { 49 50 private static final HtmlSanitizer.Policy DO_NOTHING_POLICY 51 = new HtmlSanitizer.Policy() { 52 public void openDocument() { /* do nothing */ } 53 public void closeDocument() { /* do nothing */ } 54 public void openTag(String elementName, List<String> attrs) { 55 /* do nothing */ 56 } 57 public void closeTag(String elementName) { /* do nothing */ } 58 public void text(String textChunk) { /* do nothing */ } 59 }; 60 testFuzzHtmlParser()61 public final void testFuzzHtmlParser() throws Exception { 62 String html = Resources.toString( 63 Resources.getResource("Yahoo!.html"), Charsets.UTF_8); 64 int length = html.length(); 65 66 char[] fuzzyHtml0 = new char[length]; 67 char[] fuzzyHtml1 = new char[length]; 68 69 final LinkedBlockingQueue<Throwable> failures 70 = new LinkedBlockingQueue<Throwable>(); 71 72 final int runCount = 1000; 73 // Use an executor so that any infinite loops do not cause the test runner 74 // to fail. 75 ThreadPoolExecutor executor = new ThreadPoolExecutor( 76 10, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); 77 78 for (int run = runCount; --run >= 0;) { 79 for (int i = length; --i >= 0;) { fuzzyHtml0[i] = html.charAt(i); } 80 for (int fuzz = 1 + rnd.nextInt(25); --fuzz >= 0;) { 81 if (rnd.nextBoolean()) { 82 fuzzyHtml0[rnd.nextInt(length)] = (char) rnd.nextInt(0x10000); 83 continue; 84 } 85 int s0 = rnd.nextInt(length - 1); 86 double d = Math.abs(rnd.nextGaussian()) / 3.0d; 87 int e0 = s0 + (int) (rnd.nextInt(length - s0) * d); 88 if (e0 >= length) { e0 = s0 + 1; } 89 90 int s1 = rnd.nextInt(length - 1); 91 d = Math.abs(rnd.nextGaussian()) / 3.0d; 92 int e1 = s1 + (int) (rnd.nextInt(length - s1) * d); 93 if (e1 >= length) { e1 = s1 + 1; } 94 95 if (s0 > s1) { 96 int st = s0, et = e0; 97 s0 = s1; 98 e0 = e1; 99 s1 = st; 100 e1 = et; 101 } 102 103 if (e0 > s1) { e0 = s1; } 104 105 // Swap the ranges [s0, e0) and [s1, e1) into fuzzyHtml1. 106 int i0, i1 = 0; 107 for (i0 = 0; i0 < s0; ++i0, ++i1) { 108 fuzzyHtml1[i1] = fuzzyHtml0[i0]; 109 } 110 for (i0 = s1; i0 < e1; ++i0, ++i1) { 111 fuzzyHtml1[i1] = fuzzyHtml0[i0]; 112 } 113 for (i0 = e0; i0 < s1; ++i0, ++i1) { 114 fuzzyHtml1[i1] = fuzzyHtml0[i0]; 115 } 116 for (i0 = s0; i0 < e0; ++i0, ++i1) { 117 fuzzyHtml1[i1] = fuzzyHtml0[i0]; 118 } 119 for (i0 = e1; i0 < length; ++i0, ++i1) { 120 fuzzyHtml1[i1] = fuzzyHtml0[i0]; 121 } 122 // Swap the two buffers. 123 char[] swap = fuzzyHtml0; 124 fuzzyHtml0 = fuzzyHtml1; 125 fuzzyHtml1 = swap; 126 } 127 final String fuzzyHtml = new String(fuzzyHtml0); 128 executor.execute(new Runnable() { 129 public void run() { 130 try { 131 HtmlSanitizer.sanitize(fuzzyHtml, DO_NOTHING_POLICY); 132 } catch (Exception ex) { 133 System.err.println( 134 "Using seed " + seed + "L\n" 135 + "Failed on <<<" + fuzzyHtml + ">>>"); 136 failures.add(ex); 137 } 138 } 139 }); 140 } 141 executor.shutdown(); 142 executor.awaitTermination(runCount * 4, TimeUnit.SECONDS); 143 assertTrue("seed=" + seed, executor.isTerminated()); 144 Throwable failure = failures.poll(); 145 if (failure != null) { 146 Throwables.propagate(failure); 147 } 148 } 149 150 } 151