diff options
| -rw-r--r-- | src/vmime/body.cpp | 54 | ||||
| -rw-r--r-- | src/vmime/parsingContext.cpp | 15 | ||||
| -rw-r--r-- | src/vmime/parsingContext.hpp | 25 |
3 files changed, 88 insertions, 6 deletions
diff --git a/src/vmime/body.cpp b/src/vmime/body.cpp index 0ef365e3..d644a6a7 100644 --- a/src/vmime/body.cpp +++ b/src/vmime/body.cpp @@ -121,6 +121,27 @@ size_t body::findNextBoundaryPosition( } +namespace { + +/** Nesting depth of a part, counted by walking to the root. + * + * A multipart body is parsed by recursion, so this is also the current stack + * depth. Bounded by the caller's limit, which is what makes the walk cheap. + */ +size_t nestingDepthOf(const bodyPart* part) { + + size_t depth = 0; + + for (const bodyPart* p = part; p != NULL; p = p->getParentPart()) { + ++depth; + } + + return depth; +} + +} // unnamed namespace + + void body::parseImpl( parsingContext& ctx, const shared_ptr <utility::parserInputStreamAdapter>& parser, @@ -131,6 +152,8 @@ void body::parseImpl( removeAllParts(); + const size_t maxDepth = ctx.getMaxNestingDepth(); + m_prologText.clear(); m_epilogText.clear(); @@ -315,7 +338,19 @@ void body::parseImpl( std::swap(partStart, partEnd); } - part->parse(ctx, parser, partStart, partEnd, NULL); + // Depth is stack depth here: descending without a bound lets a + // small crafted message exhaust the stack and kill the process, + // and a message must be parsed before anything can say whether + // it is trustworthy. Over the limit the part is kept, with its + // bounds, but not descended into. + if (maxDepth != 0 && nestingDepthOf(m_part) >= maxDepth) { + + part->getBody()->setParsedBounds(partStart, partEnd); + + } else { + + part->parse(ctx, parser, partStart, partEnd, NULL); + } m_parts.push_back(part); } @@ -335,10 +370,19 @@ void body::parseImpl( shared_ptr <bodyPart> part = m_part->createChildPart(); - try { - part->parse(ctx, parser, partStart, end); - } catch (std::exception&) { - throw; + // See above: the same bound applies to the recovery path, which is + // the one a malformed message is most likely to take. + if (maxDepth != 0 && nestingDepthOf(m_part) >= maxDepth) { + + part->getBody()->setParsedBounds(partStart, end); + + } else { + + try { + part->parse(ctx, parser, partStart, end); + } catch (std::exception&) { + throw; + } } m_parts.push_back(part); diff --git a/src/vmime/parsingContext.cpp b/src/vmime/parsingContext.cpp index e80eab78..8c1272d8 100644 --- a/src/vmime/parsingContext.cpp +++ b/src/vmime/parsingContext.cpp @@ -35,7 +35,8 @@ parsingContext::parsingContext() parsingContext::parsingContext(const parsingContext& ctx) : context(ctx), - m_headerParseErrorRecovery(vmime::headerParseRecoveryMethod::SKIP_LINE) { + m_headerParseErrorRecovery(vmime::headerParseRecoveryMethod::SKIP_LINE), + m_maxNestingDepth(ctx.m_maxNestingDepth) { } @@ -79,6 +80,18 @@ bool parsingContext::getUseMyHostname() const { } +size_t parsingContext::getMaxNestingDepth() const { + + return m_maxNestingDepth; +} + + +void parsingContext::setMaxNestingDepth(const size_t depth) { + + m_maxNestingDepth = depth; +} + + void parsingContext::setUseMyHostname(bool useMyHostname) { m_useMyHostname = useMyHostname; diff --git a/src/vmime/parsingContext.hpp b/src/vmime/parsingContext.hpp index 95c51959..817fffa9 100644 --- a/src/vmime/parsingContext.hpp +++ b/src/vmime/parsingContext.hpp @@ -101,6 +101,26 @@ public: */ void setUseMyHostname(bool useMyHostname); + /** Returns the maximum MIME nesting depth this context will parse. + * + * A multipart body is parsed by recursion, so nesting depth is stack + * depth: a message is a few hundred bytes per level, and a few thousand + * levels exhausts the stack and terminates the process. Since a message + * has to be parsed before anything can say whether it is trustworthy, + * that is reachable from unauthenticated input. + * + * Parts deeper than this are left unparsed rather than descended into. + * + * @return the maximum nesting depth, or 0 for unlimited + */ + size_t getMaxNestingDepth() const; + + /** Sets the maximum MIME nesting depth. See getMaxNestingDepth. + * + * @param depth maximum depth, or 0 for unlimited + */ + void setMaxNestingDepth(const size_t depth); + protected: headerParseRecoveryMethod::headerLineError m_headerParseErrorRecovery; @@ -120,6 +140,11 @@ protected: * for header fields when one is not present. */ bool m_useMyHostname{true}; + + /** Maximum MIME nesting depth; 0 means unlimited. See + * getMaxNestingDepth(). + */ + size_t m_maxNestingDepth{1024}; }; |
