// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// import com.code_intelligence.jazzer.api.FuzzedDataProvider; import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow; import java.io.OutputStreamWriter; import java.io.BufferedInputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.ArrayList; import java.net.URL; import java.net.HttpURLConnection; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.apache.catalina.Context; import org.apache.catalina.Globals; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.LifecycleException; import org.apache.tomcat.util.buf.ByteChunk; public class ConnectorSendFileFuzzer { static Tomcat tomcat = null; static Connector connector1 = null; static Context root = null; static String contextPath = null; static String baseDir = null; static int counter = Integer.MIN_VALUE; static int EXPECTED_CONTENT_LENGTH = 100000; static String [] encodings = { "US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-16" }; static String [] content_type = { "application/java-archive", "application/EDI-X12", "application/EDIFACT", "application/javascript", "application/octet-stream", "application/ogg", "application/pdf", "application/xhtml+xml", "application/x-shockwave-flash", "application/json", "application/ld+json", "application/xml", "application/zip", "application/x-www-form-urlencoded", "audio/mpeg", "audio/x-ms-wma", "audio/vnd.rn-realaudio", "audio/x-wav", "image/gif", "image/jpeg", "image/png", "image/tiff", "image/vnd.microsoft.icon", "image/x-icon", "image/vnd.djvu", "image/svg+xml", "multipart/mixed", "multipart/alternative", "multipart/related", "multipart/form-data", "text/css", "text/csv", "text/html", "text/javascript", "text/plain", "text/xml", "video/mpeg", "video/mp4", "video/quicktime", "video/x-ms-wmv", "video/x-msvideo", "video/x-flv", "video/webm", "application/vnd.android.package-archive", "application/vnd.oasis.opendocument.text", "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.presentation", "application/vnd.oasis.opendocument.graphics", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.mozilla.xul+xml", }; public static void fuzzerTearDown() { try { tomcat.stop(); tomcat.destroy(); tomcat = null; System.gc(); } catch (LifecycleException e) { throw new FuzzerSecurityIssueLow("Teardown Error!"); } } public static void fuzzerInitialize() { tomcat = new Tomcat(); baseDir = "temp"; tomcat.setBaseDir(baseDir); connector1 = tomcat.getConnector(); connector1.setPort(0); String docBase = new File(".").getAbsolutePath(); root = tomcat.addContext("", docBase); try { tomcat.start(); } catch (LifecycleException e) { throw new FuzzerSecurityIssueLow("Tomcat Start error!"); } } public static void fuzzerTestOneInput(FuzzedDataProvider data) { // java.util.logging.Logger.getLogger("org.apache").setLevel(java.util.logging.Level.OFF); int c_num = data.consumeInt(0, content_type.length - 1); int e_num = data.consumeInt(0, encodings.length - 1); byte [] ba = data.consumeBytes(8192); File file = null; try { file = generateFile(new File("./" + baseDir), ba); } catch (IOException e) { throw new FuzzerSecurityIssueLow("generateFile Error!"); } if (counter < Integer.MAX_VALUE) { counter++; } else { System.exit(1); } WritingServlet servlet = new WritingServlet(file, c_num, e_num); Tomcat.addServlet(root, "servlet" + "-" + counter, servlet); root.addServletMappingDecoded("/servlet", "servlet" + "-" + counter); ByteChunk bc = new ByteChunk(); Map> respHeaders = new HashMap<>(); int rc = -1; try { rc = getUrl("http://localhost:" + tomcat.getConnector().getLocalPort() + "/servlet", bc, null, respHeaders); } catch (IOException e) { throw new FuzzerSecurityIssueLow("getUrl error!"); } assert rc == HttpServletResponse.SC_OK : new FuzzerSecurityIssueLow("rc is not ok!"); bc.recycle(); respHeaders.clear(); file.delete(); System.gc(); } public static class WritingServlet extends HttpServlet { private static final long serialVersionUID = 1L; private final File f; private final int c; private final int e; public WritingServlet(File f, int c, int e) { this.f = f; this.c = c; this.e = e; } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType(content_type[c]); resp.setCharacterEncoding(encodings[e]); resp.setContentLengthLong(f.length()); req.setAttribute(Globals.SENDFILE_FILENAME_ATTR, f.getAbsolutePath()); req.setAttribute(Globals.SENDFILE_FILE_START_ATTR, Long.valueOf(0)); req.setAttribute(Globals.SENDFILE_FILE_END_ATTR, Long.valueOf(f.length())); byte[] c = new byte[8192]; try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(f))) { int len = 0; int written = 0; long start = System.currentTimeMillis(); do { len = in.read(c); if (len > 0) { resp.getOutputStream().write(c, 0, len); written += len; } } while (len > 0); } } } public static File generateFile(File dir, byte [] ba) throws IOException { String name = "testSendFile-"; String suffix = ""; File f = File.createTempFile(name, suffix, dir); try (FileOutputStream w = new FileOutputStream(f)) { w.write(ba); w.flush(); } return f; } public static int getUrl(String path, ByteChunk out, Map> reqHead, Map> resHead) throws IOException { return methodUrl(path, out, 300_000, reqHead, resHead, "GET", true); } public static int methodUrl(String path, ByteChunk out, int readTimeout, Map> reqHead, Map> resHead, String method, boolean followRedirects) throws IOException { URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setUseCaches(false); connection.setReadTimeout(readTimeout); connection.setRequestMethod(method); connection.setInstanceFollowRedirects(followRedirects); if (reqHead != null) { for (Map.Entry> entry : reqHead.entrySet()) { StringBuilder valueList = new StringBuilder(); for (String value : entry.getValue()) { if (valueList.length() > 0) { valueList.append(','); } valueList.append(value); } connection.setRequestProperty(entry.getKey(), valueList.toString()); } } connection.connect(); int rc = connection.getResponseCode(); if (resHead != null) { // Skip the entry with null key that is used for the response line // that some Map implementations may not accept. for (Map.Entry> entry : connection.getHeaderFields().entrySet()) { if (entry.getKey() != null) { resHead.put(entry.getKey(), entry.getValue()); } } } InputStream is; if (rc < 400) { is = connection.getInputStream(); } else { is = connection.getErrorStream(); } if (is != null) { try (BufferedInputStream bis = new BufferedInputStream(is)) { byte[] buf = new byte[2048]; int rd = 0; while((rd = bis.read(buf)) > 0) { out.append(buf, 0, rd); } } } return rc; } }