Projects
home:Eustace:branches:Eulaceura:Factory
jetty
_service:obs_scm:CVE-2023-26048.patch
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:CVE-2023-26048.patch of Package jetty
From: Markus Koschany <apo@debian.org> Date: Tue, 26 Sep 2023 20:37:47 +0200 Subject: CVE-2023-26048 Origin: https://github.com/eclipse/jetty.project/pull/9345 --- .../jetty/http/MultiPartFormInputStream.java | 76 +++++++----- .../java/org/eclipse/jetty/server/MultiParts.java | 14 ++- .../java/org/eclipse/jetty/server/Request.java | 127 ++++++++++++--------- .../jetty/server/handler/ContextHandler.java | 4 + .../jetty/util/MultiPartInputStreamParser.java | 24 +++- 5 files changed, 158 insertions(+), 87 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java index 928f59c..a1092f7 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java @@ -60,11 +60,14 @@ import org.eclipse.jetty.util.log.Logger; public class MultiPartFormInputStream { private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class); + private static final int DEFAULT_MAX_FORM_KEYS = 1000; private static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); + private final MultiMap<Part> _parts; + private final int _maxParts; + private int _numParts = 0; private InputStream _in; private MultipartConfigElement _config; private String _contentType; - private MultiMap<Part> _parts; private Throwable _err; private File _tmpDir; private File _contextTmpDir; @@ -332,26 +335,42 @@ public class MultiPartFormInputStream * @param contextTmpDir javax.servlet.context.tempdir */ public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) + { + this(in, contentType, config, contextTmpDir, DEFAULT_MAX_FORM_KEYS); + } + + /** + * @param in Request input stream + * @param contentType Content-Type header + * @param config MultipartConfigElement + * @param contextTmpDir javax.servlet.context.tempdir + * @param maxParts the maximum number of parts that can be parsed from the multipart content (0 for no parts allowed, -1 for unlimited parts). + */ + public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, int maxParts) + { _contentType = contentType; _config = config; _contextTmpDir = contextTmpDir; + _maxParts = maxParts; if (_contextTmpDir == null) _contextTmpDir = new File(System.getProperty("java.io.tmpdir")); if (_config == null) _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); + MultiMap<Part> parts = new MultiMap<>(); if (in instanceof ServletInputStream) { if (((ServletInputStream)in).isFinished()) { - _parts = EMPTY_MAP; + parts = EMPTY_MAP; _parsed = true; - return; } } - _in = new BufferedInputStream(in); + if (!_parsed) + _in = new BufferedInputStream(in); + _parts = parts; } /** @@ -495,16 +514,15 @@ public class MultiPartFormInputStream if (_parsed) return; _parsed = true; - + + MultiPartParser parser = null; + Handler handler = new Handler(); try { - // initialize - _parts = new MultiMap<>(); - // if its not a multipart request, don't parse it if (_contentType == null || !_contentType.startsWith("multipart/form-data")) return; - + // sort out the location to which to write the files if (_config.getLocation() == null) _tmpDir = _contextTmpDir; @@ -518,10 +536,10 @@ public class MultiPartFormInputStream else _tmpDir = new File(_contextTmpDir, _config.getLocation()); } - + if (!_tmpDir.exists()) _tmpDir.mkdirs(); - + String contentTypeBoundary = ""; int bstart = _contentType.indexOf("boundary="); if (bstart >= 0) @@ -530,22 +548,19 @@ public class MultiPartFormInputStream bend = (bend < 0 ? _contentType.length() : bend); contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart, bend)).trim()); } - - Handler handler = new Handler(); - MultiPartParser parser = new MultiPartParser(handler, contentTypeBoundary); - + + parser = new MultiPartParser(handler, contentTypeBoundary); byte[] data = new byte[_bufferSize]; int len; long total = 0; - + while (true) { - + len = _in.read(data); - + if (len > 0) { - // keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize total += len; if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) @@ -553,30 +568,28 @@ public class MultiPartFormInputStream _err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")"); return; } - + ByteBuffer buffer = BufferUtil.toBuffer(data); buffer.limit(len); if (parser.parse(buffer, false)) break; - + if (buffer.hasRemaining()) throw new IllegalStateException("Buffer did not fully consume"); - } else if (len == -1) { parser.parse(BufferUtil.EMPTY_BUFFER, true); break; } - } - + // check for exceptions if (_err != null) { return; } - + // check we read to the end of the message if (parser.getState() != MultiPartParser.State.END) { @@ -585,19 +598,23 @@ public class MultiPartFormInputStream else _err = new IOException("Incomplete Multipart"); } - + if (LOG.isDebugEnabled()) { LOG.debug("Parsing Complete {} err={}", parser, _err); } - } catch (Throwable e) { _err = e; + + // Notify parser if failure occurs + if (parser != null) + parser.parse(BufferUtil.EMPTY_BUFFER, true); } } - + + class Handler implements MultiPartParser.Handler { private MultiPart _part = null; @@ -735,6 +752,9 @@ public class MultiPartFormInputStream public void startPart() { reset(); + _numParts++; + if (_maxParts >= 0 && _numParts > _maxParts) + throw new IllegalStateException(String.format("Form with too many parts [%d > %d]", _numParts, _maxParts)); } @Override diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java index f28b945..1b91649 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java @@ -57,7 +57,12 @@ public interface MultiParts extends Closeable public MultiPartsHttpParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request) throws IOException { - _httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir); + this(in, contentType, config, contextTmpDir, request, ContextHandler.DEFAULT_MAX_FORM_KEYS); + } + + public MultiPartsHttpParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request, int maxParts) throws IOException + { + _httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir, maxParts); _context = request.getContext(); _httpParser.getParts(); } @@ -116,7 +121,12 @@ public interface MultiParts extends Closeable public MultiPartsUtilParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request) throws IOException { - _utilParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir); + this(in, contentType, config, contextTmpDir, request, ContextHandler.DEFAULT_MAX_FORM_KEYS); + } + + public MultiPartsUtilParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request, int maxParts) throws IOException + { + _utilParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir, maxParts); _context = request.getContext(); _utilParser.getParts(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 5b996bb..8a7e6f9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -425,6 +425,14 @@ public class Request implements HttpServletRequest return parameters==null?NO_PARAMS:parameters; } + private boolean isContentEncodingSupported() + { + String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING); + if (contentEncoding == null) + return true; + return HttpHeaderValue.IDENTITY.is(contentEncoding); + } + /* ------------------------------------------------------------ */ private void extractQueryParameters() { @@ -458,33 +466,34 @@ public class Request implements HttpServletRequest { String contentType = getContentType(); if (contentType == null || contentType.isEmpty()) - _contentParameters=NO_PARAMS; + _contentParameters = NO_PARAMS; else { - _contentParameters=new MultiMap<>(); + _contentParameters = new MultiMap<>(); int contentLength = getContentLength(); if (contentLength != 0 && _inputState == __NONE) { - contentType = HttpFields.valueParameters(contentType, null); - if (MimeTypes.Type.FORM_ENCODED.is(contentType) && + String baseType = HttpFields.valueParameters(contentType, null); + if (MimeTypes.Type.FORM_ENCODED.is(baseType) && _channel.getHttpConfiguration().isFormEncodedMethod(getMethod())) { - if (_metaData!=null) + if (_metaData != null && !isContentEncodingSupported()) { - String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING); - if (contentEncoding!=null && !HttpHeaderValue.IDENTITY.is(contentEncoding)) - throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501, "Unsupported Content-Encoding"); + throw new BadMessageException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Content-Encoding"); } + extractFormParameters(_contentParameters); } - else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) && - getAttribute(__MULTIPART_CONFIG_ELEMENT) != null && - _multiParts == null) + else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) && + getAttribute(__MULTIPART_CONFIG_ELEMENT) != null && + _multiParts == null) { try { - if (_metaData!=null && getHttpFields().contains(HttpHeader.CONTENT_ENCODING)) - throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501,"Unsupported Content-Encoding"); + if (_metaData != null && !isContentEncodingSupported()) + { + throw new BadMessageException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Content-Encoding"); + } getParts(_contentParameters); } catch (IOException | ServletException e) @@ -502,57 +511,30 @@ public class Request implements HttpServletRequest { try { - int maxFormContentSize = -1; - int maxFormKeys = -1; + int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE; + int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS; if (_context != null) { - maxFormContentSize = _context.getContextHandler().getMaxFormContentSize(); - maxFormKeys = _context.getContextHandler().getMaxFormKeys(); + ContextHandler contextHandler = _context.getContextHandler(); + maxFormContentSize = contextHandler.getMaxFormContentSize(); + maxFormKeys = contextHandler.getMaxFormKeys(); } - - if (maxFormContentSize < 0) + else { - Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); - if (obj == null) - maxFormContentSize = 200000; - else if (obj instanceof Number) - { - Number size = (Number)obj; - maxFormContentSize = size.intValue(); - } - else if (obj instanceof String) - { - maxFormContentSize = Integer.parseInt((String)obj); - } - } - - if (maxFormKeys < 0) - { - Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys"); - if (obj == null) - maxFormKeys = 1000; - else if (obj instanceof Number) - { - Number keys = (Number)obj; - maxFormKeys = keys.intValue(); - } - else if (obj instanceof String) - { - maxFormKeys = Integer.parseInt((String)obj); - } + maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize); + maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys); } int contentLength = getContentLength(); - if (contentLength > maxFormContentSize && maxFormContentSize > 0) - { - throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize); - } + if (maxFormContentSize >= 0 && contentLength > maxFormContentSize) + throw new IllegalStateException("Form is larger than max length " + maxFormContentSize); + InputStream in = getInputStream(); if (_input.isAsync()) throw new IllegalStateException("Cannot extract parameters with async IO"); - UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys); + UrlEncoded.decodeTo(in, params, getCharacterEncoding(), maxFormContentSize, maxFormKeys); } catch (IOException e) { @@ -561,6 +543,16 @@ public class Request implements HttpServletRequest } } + private int lookupServerAttribute(String key, int dftValue) + { + Object attribute = _channel.getServer().getAttribute(key); + if (attribute instanceof Number) + return ((Number)attribute).intValue(); + else if (attribute instanceof String) + return Integer.parseInt((String)attribute); + return dftValue; + } + /* ------------------------------------------------------------ */ @Override public AsyncContext getAsyncContext() @@ -2351,9 +2343,23 @@ public class Request implements HttpServletRequest if (config == null) throw new IllegalStateException("No multipart config for servlet"); + int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE; + int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS; + if (_context != null) + { + ContextHandler contextHandler = _context.getContextHandler(); + maxFormContentSize = contextHandler.getMaxFormContentSize(); + maxFormKeys = contextHandler.getMaxFormKeys(); + } + else + { + maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize); + maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys); + } + _multiParts = newMultiParts(getInputStream(), getContentType(), config, - (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); + (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null),maxFormKeys); setAttribute(__MULTIPARTS, _multiParts); Collection<Part> parts = _multiParts.getParts(); //causes parsing @@ -2388,11 +2394,16 @@ public class Request implements HttpServletRequest else defaultCharset = StandardCharsets.UTF_8; + long formContentSize = 0; ByteArrayOutputStream os = null; for (Part p:parts) { if (p.getSubmittedFileName() == null) { + formContentSize = Math.addExact(formContentSize, p.getSize()); + if (maxFormContentSize >= 0 && formContentSize > maxFormContentSize) + throw new IllegalStateException("Form is larger than max length " + maxFormContentSize); + // Servlet Spec 3.0 pg 23, parts without filename must be put into params. String charset = null; if (p.getContentType() != null) @@ -2418,7 +2429,9 @@ public class Request implements HttpServletRequest } - private MultiParts newMultiParts(ServletInputStream inputStream, String contentType, MultipartConfigElement config, Object object) throws IOException + private MultiParts newMultiParts(ServletInputStream inputStream, String + contentType, MultipartConfigElement config, Object object, + int maxParts) throws IOException { MultiPartFormDataCompliance compliance = getHttpChannel().getHttpConfiguration().getMultipartFormDataCompliance(); if(LOG.isDebugEnabled()) @@ -2428,12 +2441,14 @@ public class Request implements HttpServletRequest { case RFC7578: return new MultiParts.MultiPartsHttpParser(getInputStream(), getContentType(), config, - (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this); + (_context != + null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this, maxParts); case LEGACY: default: return new MultiParts.MultiPartsUtilParser(getInputStream(), getContentType(), config, - (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this); + (_context != + null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this, maxParts); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index b6ca046..c4cbebd 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -132,6 +132,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu */ public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes"; + public static final String MAX_FORM_KEYS_KEY = "org.eclipse.jetty.server.Request.maxFormKeys"; + public static final String MAX_FORM_CONTENT_SIZE_KEY = "org.eclipse.jetty.server.Request.maxFormContentSize"; + public static final int DEFAULT_MAX_FORM_KEYS = 1000; + public static final int DEFAULT_MAX_FORM_CONTENT_SIZE = 200000; /* ------------------------------------------------------------ */ /** * Get the current ServletContext implementation. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java index 17e7bb1..d45b8ff 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java @@ -65,8 +65,11 @@ import org.eclipse.jetty.util.log.Logger; public class MultiPartInputStreamParser { private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class); + private static final int DEFAULT_MAX_FORM_KEYS = 1000; public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir")); - public static final MultiMap<Part> EMPTY_MAP = new MultiMap(Collections.emptyMap()); + public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); + private final int _maxParts; + private int _numParts; protected InputStream _in; protected MultipartConfigElement _config; protected String _contentType; @@ -411,9 +414,23 @@ public class MultiPartInputStreamParser */ public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) { + this(in, contentType, config, contextTmpDir, DEFAULT_MAX_FORM_KEYS); + } + + /** + * @param in Request input stream + * @param contentType Content-Type header + * @param config MultipartConfigElement + * @param contextTmpDir javax.servlet.context.tempdir + * @param maxParts the maximum number of parts that can be parsed from the multipart content (0 for no parts allowed, -1 for unlimited parts). + */ + public MultiPartInputStreamParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, int maxParts) + + { _contentType = contentType; _config = config; _contextTmpDir = contextTmpDir; + _maxParts = maxParts; if (_contextTmpDir == null) _contextTmpDir = new File (System.getProperty("java.io.tmpdir")); @@ -712,6 +729,11 @@ public class MultiPartInputStreamParser continue; } + // Check if we can create a new part. + _numParts++; + if (_maxParts >= 0 && _numParts > _maxParts) + throw new IllegalStateException(String.format("Form with too many parts [%d > %d]", _numParts, _maxParts)); + //Have a new Part MultiPart part = new MultiPart(name, filename); part.setHeaders(headers);
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2