From: Alex Rousskov Date: Mon, 16 May 2022 12:53:26 +0000 (+0000) Subject: Fixed (and renamed) Ssl::ReadX509Certificate() API (#1048) X-Git-Tag: SQUID_6_0_1~185 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ac1b16b5892abf311cbfd31ce998f914e98fc123;p=thirdparty%2Fsquid.git Fixed (and renamed) Ssl::ReadX509Certificate() API (#1048) This pointer-returning non-throwing function was used to load an unknown number of certificates from a file. A nil result normally indicated the end of the certificate sequence but also a certificate loading failure. The caller could not distinguish the two nil-result outcomes without OpenSSL error stack analysis, which is difficult to do correctly, and which none of the callers bothered to do, resulting in some certificate configuration errors silently ignored by Squid. The adjusted API uses exceptions to report errors. A nil result is still used to report the end of the certificate sequence. To help callers that must have at least one certificate, one function is now dedicated to loading required certificate(s) while a companion function is added to load optional ones. Avoid exponential growth in the number of changed functions by wrapping callers inside the not-ready-to-throw code chains (that cannot be easily converted) with try/catch-return-false blocks. Also removed the need for Ssl::readCertFromMemory() by adding Ssl::ReadOnlyBioTiedTo(). Functions reading things (from various input sources) should be kept separately from functions creating input sources (for various reading functions). Otherwise, we will end up creating a lot more functions (at least readers*creators) with virtually no gain in performance or convenience. OpenSSL BIO API exists specifically to separate I/O mechanisms from I/O users. Use it to our advantage. We now also clear old/stale OpenSSL error stack before performing the certificate loading operation that may trigger one or more errors. Also forget stale errno when forgetting OpenSSL errors. This reset avoids misleading error reports based on stale errno values. Also detail certificate loading errors, now that they are not ignored. ReportSysError was added to avoid SysErrorDetail::NewIfAny() memory allocations when reporting basic system call errors. Perhaps there is a way to reuse SysErrorDetail here via (enhanced) Optional, but the resulting complexity and the current SBuf-based reporting overheads do not justify the reuse of "if errno then strerror(errno)" logic. We may revisit this when Optional supports non-trivial classes. Also moved most SysErrorDetail implementation details to the newly added .cc file (to reduce user exposure), replacing strerror() with xstrerr(). ForgetErrors guts were moved from Security to Ssl because we cannot link helpers with src/security (yet). We may have to refactor Security to make such reuse possible in the future. --- diff --git a/src/error/Makefile.am b/src/error/Makefile.am index bdc6d40edd..e42d308e92 100644 --- a/src/error/Makefile.am +++ b/src/error/Makefile.am @@ -22,6 +22,7 @@ liberror_la_SOURCES = \ Error.cc \ Error.h \ ExceptionErrorDetail.h \ + SysErrorDetail.cc \ SysErrorDetail.h \ forward.h diff --git a/src/error/SysErrorDetail.cc b/src/error/SysErrorDetail.cc new file mode 100644 index 0000000000..27fce4e4e1 --- /dev/null +++ b/src/error/SysErrorDetail.cc @@ -0,0 +1,39 @@ +/* + * Copyright (C) 1996-2022 The Squid Software Foundation and contributors + * + * Squid software is distributed under GPLv2+ license and includes + * contributions from numerous individuals and organizations. + * Please see the COPYING and CONTRIBUTORS files for sys_error_details. + */ + +#include "squid.h" +#include "error/SysErrorDetail.h" +#include "sbuf/SBuf.h" +#include "sbuf/Stream.h" + +SBuf +SysErrorDetail::Brief(int errorNo) +{ + return SysErrorDetail(errorNo).brief(); +} + +SBuf +SysErrorDetail::brief() const +{ + return ToSBuf("errno=", errorNo); +} + +SBuf +SysErrorDetail::verbose(const HttpRequestPointer &) const +{ + return SBuf(xstrerr(errorNo)); +} + +std::ostream & +operator <<(std::ostream &os, const ReportSysError rse) +{ + if (const auto errorNo = rse.errorNo) + os << Debug::Extra << "system call error: " << xstrerr(errorNo); + return os; +} + diff --git a/src/error/SysErrorDetail.h b/src/error/SysErrorDetail.h index 2eafc12587..f52a629aa1 100644 --- a/src/error/SysErrorDetail.h +++ b/src/error/SysErrorDetail.h @@ -10,8 +10,7 @@ #define _SQUID_SRC_ERROR_SYSERRORDETAIL_H #include "error/Detail.h" -#include "sbuf/SBuf.h" -#include "sbuf/Stream.h" +#include "sbuf/forward.h" /// system call failure detail based on standard errno(3)/strerror(3) APIs class SysErrorDetail: public ErrorDetail @@ -26,18 +25,12 @@ public: return errorNo ? new SysErrorDetail(errorNo) : nullptr; } - static SBuf Brief(int errorNo) { - return SysErrorDetail(errorNo).brief(); - } + /// \copydoc ErrorDetail::brief() + static SBuf Brief(int errorNo); /* ErrorDetail API */ - virtual SBuf brief() const override { - return ToSBuf("errno=", errorNo); - } - - virtual SBuf verbose(const HttpRequestPointer &) const override { - return SBuf(strerror(errorNo)); - } + virtual SBuf brief() const override; + virtual SBuf verbose(const HttpRequestPointer &) const override; private: // hidden by NewIfAny() to avoid creating SysErrorDetail from zero errno @@ -46,5 +39,16 @@ private: int errorNo; ///< errno(3) set by the last failed system call or equivalent }; +/// a stream manipulator for printing a system call error (if any) +class ReportSysError +{ +public: + explicit ReportSysError(const int anErrorNo): errorNo(anErrorNo) {} + int errorNo; +}; + +/// reports a system call error (if any) on a dedicated Debug::Extra line +std::ostream &operator <<(std::ostream &, ReportSysError); + #endif /* _SQUID_SRC_ERROR_SYSERRORDETAIL_H */ diff --git a/src/security/Io.cc b/src/security/Io.cc index 7ea81dffec..1c2cc09892 100644 --- a/src/security/Io.cc +++ b/src/security/Io.cc @@ -12,6 +12,7 @@ #include "base/IoManip.h" #include "fde.h" #include "security/Io.h" +#include "ssl/gadgets.h" namespace Security { @@ -50,15 +51,13 @@ Security::IoResult::print(std::ostream &os) const os << ", important"; } -// TODO: Replace high-level ERR_get_error() calls with a new std::ostream -// ReportErrors manipulator inside debugs(), followed by a ForgetErrors() call. +// TODO: Replace high-level ERR_get_error() calls with ForgetErrors() calls or +// exceptions carrying ReportAndForgetErrors() reports. void Security::ForgetErrors() { #if USE_OPENSSL - unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary - while (const auto errorToForget = ERR_get_error()) - debugs(83, 7, '#' << (++reported) << ": " << asHex(errorToForget)); + Ssl::ForgetErrors(); #endif } diff --git a/src/security/KeyData.cc b/src/security/KeyData.cc index b5ded80a1b..8e3b577acc 100644 --- a/src/security/KeyData.cc +++ b/src/security/KeyData.cc @@ -14,10 +14,7 @@ #include "ssl/bio.h" #include "ssl/gadgets.h" -/** - * Read certificate from file. - * See also: Ssl::ReadX509Certificate function, gadgets.cc file - */ +/// load a signing certificate from certFile bool Security::KeyData::loadX509CertFromFile() { @@ -33,7 +30,16 @@ Security::KeyData::loadX509CertFromFile() return false; } - cert = Ssl::ReadX509Certificate(bio); // error detected/reported below + try { + cert = Ssl::ReadCertificate(bio); + return true; + } + catch (...) { + // TODO: Convert the rest of this method to throw on errors instead. + debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "':" << + Debug::Extra << "problem: " << CurrentException); + return false; + } #elif USE_GNUTLS const char *certFilename = certFile.c_str(); @@ -77,10 +83,7 @@ Security::KeyData::loadX509CertFromFile() return bool(cert); } -/** - * Read certificate from file. - * See also: Ssl::ReadX509Certificate function, gadgets.cc file - */ +/// load any intermediate certs that form the chain with the loaded signing cert void Security::KeyData::loadX509ChainFromFile() { @@ -105,7 +108,7 @@ Security::KeyData::loadX509ChainFromFile() // and add to the chain any other certificate exist in the file CertPointer latestCert = cert; - while (const auto ca = Ssl::ReadX509Certificate(bio)) { + while (const auto ca = Ssl::ReadOptionalCertificate(bio)) { // get Issuer name of the cert for debug display char *nameStr = X509_NAME_oneline(X509_get_subject_name(ca.get()), nullptr, 0); @@ -197,7 +200,14 @@ Security::KeyData::loadFromFiles(const AnyP::PortCfg &port, const char *portType } // certificate chain in the PEM file is optional - loadX509ChainFromFile(); + try { + loadX509ChainFromFile(); + } + catch (...) { + // XXX: Reject malformed configurations by letting exceptions propagate. + debugs(83, DBG_CRITICAL, "ERROR: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' cannot load intermediate certificates from '" << certFile << "':" << + Debug::Extra << "problem: " << CurrentException); + } // pkey is mandatory, not having it makes cert and chain pointless. if (!loadX509PrivateKeyFromFile()) { diff --git a/src/security/cert_generators/file/Makefile.am b/src/security/cert_generators/file/Makefile.am index 168cf2030c..15951083a4 100644 --- a/src/security/cert_generators/file/Makefile.am +++ b/src/security/cert_generators/file/Makefile.am @@ -27,6 +27,7 @@ security_file_certgen_LDADD = \ $(top_builddir)/src/ssl/libsslutil.la \ $(top_builddir)/src/sbuf/libsbuf.la \ $(top_builddir)/src/debug/libdebug.la \ + $(top_builddir)/src/error/liberror.la \ $(top_builddir)/src/comm/libminimal.la \ $(top_builddir)/src/mem/libminimal.la \ $(top_builddir)/src/base/libbase.la \ diff --git a/src/security/cert_generators/file/certificate_db.cc b/src/security/cert_generators/file/certificate_db.cc index 8af1b6bc15..01e0cde27a 100644 --- a/src/security/cert_generators/file/certificate_db.cc +++ b/src/security/cert_generators/file/certificate_db.cc @@ -649,11 +649,14 @@ Ssl::CertificateDb::ReadEntry(std::string filename, Security::CertPointer &cert, Ssl::BIO_Pointer bio; if (!Ssl::OpenCertsFileForReading(bio, filename.c_str())) return false; - if (!(cert = Ssl::ReadX509Certificate(bio))) - return false; + + cert = Ssl::ReadCertificate(bio); + if (!Ssl::ReadPrivateKey(bio, pkey, NULL)) return false; - orig = Ssl::ReadX509Certificate(bio); // optional; may be nil + + orig = Ssl::ReadOptionalCertificate(bio); + return true; } diff --git a/src/ssl/cert_validate_message.cc b/src/ssl/cert_validate_message.cc index b7b691eaa3..8edf37e543 100644 --- a/src/ssl/cert_validate_message.cc +++ b/src/ssl/cert_validate_message.cc @@ -10,6 +10,7 @@ #include "acl/FilledChecklist.h" #include "globals.h" #include "helper.h" +#include "sbuf/Stream.h" #include "security/CertError.h" #include "ssl/cert_validate_message.h" #include "ssl/ErrorDetail.h" @@ -90,6 +91,22 @@ get_error_id(const char *label, size_t len) bool Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) +{ + try { + tryParsingResponse(resp); + return true; + } + catch (...) { + debugs(83, DBG_IMPORTANT, "ERROR: Cannot parse sslcrtvalidator_program response:" << + Debug::Extra << "problem: " << CurrentException); + return false; + } +} + +/// implements primary parseResponse() functionality until that method callers +/// are ready to handle exceptions +void +Ssl::CertValidationMsg::tryParsingResponse(CertValidationResponse &resp) { std::vector certs; @@ -103,8 +120,7 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) size_t param_len = strcspn(param, "=\r\n"); if (param[param_len] != '=') { - debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: " << param); - return false; + throw TextException(ToSBuf("Missing parameter value: ", param), Here()); } const char *value=param+param_len+1; @@ -112,15 +128,13 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) strncmp(param, param_cert.c_str(), param_cert.length()) == 0) { CertItem ci; ci.name.assign(param, param_len); - Security::CertPointer x509; - readCertFromMemory(x509, value); + const auto x509 = ReadCertificate(ReadOnlyBioTiedTo(value)); ci.setCert(x509.get()); certs.push_back(ci); const char *b = strstr(value, "-----END CERTIFICATE-----"); if (b == NULL) { - debugs(83, DBG_IMPORTANT, "WARNING: cert Validator response parse error: Failed to find certificate boundary " << value); - return false; + throw TextException(ToSBuf("Missing END CERTIFICATE boundary: ", value), Here()); } b += strlen("-----END CERTIFICATE-----"); param = b + 1; @@ -140,8 +154,7 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) strncmp(param, param_error_name.c_str(), param_error_name.length()) == 0) { currentItem.error_no = Ssl::GetErrorCode(v.c_str()); if (currentItem.error_no == SSL_ERROR_NONE) { - debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: Unknown SSL Error: " << v); - return false; + throw TextException(ToSBuf("Unknown TLS error name: ", v), Here()); } } else if (param_len > param_error_reason.length() && strncmp(param, param_error_reason.c_str(), param_error_reason.length()) == 0) { @@ -167,8 +180,7 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) std::all_of(v.begin(), v.end(), isdigit)) { currentItem.error_depth = atoi(v.c_str()); } else { - debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: Unknown parameter name " << std::string(param, param_len).c_str()); - return false; + throw TextException(ToSBuf("Unknown parameter name: ", std::string(param, param_len)), Here()); } param = value + value_len; @@ -178,12 +190,9 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp) typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { if (i->error_no == SSL_ERROR_NONE) { - debugs(83, DBG_IMPORTANT, "WARNING: cert validator incomplete response: Missing error name from error_id: " << i->id); - return false; + throw TextException(ToSBuf("Incomplete response; missing error name from error_id: ", i->id), Here()); } } - - return true; } X509 * diff --git a/src/ssl/cert_validate_message.h b/src/ssl/cert_validate_message.h index 87a5b48624..a448f48f4a 100644 --- a/src/ssl/cert_validate_message.h +++ b/src/ssl/cert_validate_message.h @@ -123,6 +123,9 @@ public: static const std::string param_proto_version; /// Parameter name for SSL cipher static const std::string param_cipher; + +private: + void tryParsingResponse(CertValidationResponse &); }; }//namespace Ssl diff --git a/src/ssl/crtd_message.cc b/src/ssl/crtd_message.cc index df7068e9bb..832ce94763 100644 --- a/src/ssl/crtd_message.cc +++ b/src/ssl/crtd_message.cc @@ -228,7 +228,7 @@ Ssl::CrtdMessage::parseRequest(CertificateProperties &certProperties) if ((pos = certs_part.find(CERT_BEGIN_STR)) != std::string::npos) { pos += CERT_BEGIN_STR.length(); if ((pos= certs_part.find(CERT_BEGIN_STR, pos)) != std::string::npos) - Ssl::readCertFromMemory(certProperties.mimicCert, certs_part.c_str() + pos); + certProperties.mimicCert = ReadCertificate(ReadOnlyBioTiedTo(certs_part.c_str() + pos)); } } diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc index b03489bbe5..eb3ca3b546 100644 --- a/src/ssl/gadgets.cc +++ b/src/ssl/gadgets.cc @@ -7,8 +7,47 @@ */ #include "squid.h" +#include "base/IoManip.h" +#include "error/SysErrorDetail.h" +#include "security/Io.h" +#include "sbuf/Stream.h" #include "ssl/gadgets.h" +void +Ssl::ForgetErrors() +{ + if (ERR_peek_last_error()) { + debugs(83, 5, "forgetting stale OpenSSL errors:" << ReportAndForgetErrors); + // forget errors if section/level-specific debugging above was disabled + while (ERR_get_error()) {} + } + + // Technically, the caller should just ignore (potentially stale) errno when + // no system calls have failed. However, due to OpenSSL error-reporting API + // deficiencies, many callers cannot detect when a TLS error was caused by a + // system call failure. We forget the stale errno (just like we forget stale + // OpenSSL errors above) so that the caller only uses fresh errno values. + errno = 0; +} + +std::ostream & +Ssl::ReportAndForgetErrors(std::ostream &os) +{ + unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary + while (const auto errorToForget = ERR_get_error()) + os << Debug::Extra << "OpenSSL-saved error #" << (++reported) << ": " << asHex(errorToForget); + return os; +} + +[[ noreturn ]] static void +ThrowErrors(const char * const problem, const int savedErrno, const SourceLocation &where) +{ + throw TextException(ToSBuf(problem, ": ", + Ssl::ReportAndForgetErrors, + ReportSysError(savedErrno)), + where); +} + EVP_PKEY * Ssl::createSslPrivateKey() { Security::PrivateKeyPointer pkey(EVP_PKEY_new()); @@ -115,8 +154,15 @@ bool Ssl::readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Security Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); BIO_puts(bio.get(), bufferToRead); - if (!(cert = Ssl::ReadX509Certificate(bio))) + try { + cert = ReadCertificate(bio); + } catch (...) { + debugs(83, DBG_IMPORTANT, "ERROR: Cannot deserialize a signing certificate:" << + Debug::Extra << "problem: " << CurrentException); + cert.reset(); + pkey.reset(); return false; + } EVP_PKEY * pkeyPtr = NULL; pkey.resetWithoutLocking(PEM_read_bio_PrivateKey(bio.get(), &pkeyPtr, 0, 0)); @@ -126,15 +172,18 @@ bool Ssl::readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Security return true; } -bool Ssl::readCertFromMemory(Security::CertPointer & cert, char const * bufferToRead) +// TODO: Convert matching BIO_s_mem() callers. +Ssl::BIO_Pointer +Ssl::ReadOnlyBioTiedTo(const char * const bufferToRead) { - Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); - BIO_puts(bio.get(), bufferToRead); - - if (!(cert = Ssl::ReadX509Certificate(bio))) - return false; - - return true; + ForgetErrors(); + // OpenSSL BIO API is not const-correct, but OpenSSL does not free or modify + // BIO_new_mem_buf() data because it is marked with BIO_FLAGS_MEM_RDONLY. + const auto castedBuffer = const_cast(bufferToRead); + if (const auto bio = BIO_new_mem_buf(castedBuffer, -1)) // no memcpy() + return BIO_Pointer(bio); + const auto savedErrno = errno; + ThrowErrors("cannot allocate OpenSSL BIO structure", savedErrno, Here()); } // According to RFC 5280 (Section A.1), the common name length in a certificate @@ -689,10 +738,38 @@ Ssl::OpenCertsFileForReading(Ssl::BIO_Pointer &bio, const char *filename) } Security::CertPointer -Ssl::ReadX509Certificate(const BIO_Pointer &bio) +Ssl::ReadOptionalCertificate(const BIO_Pointer &bio) +{ + Assure(bio); + ForgetErrors(); + if (const auto cert = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)) + return Security::CertPointer(cert); + const auto savedErrno = errno; + + // PEM_R_NO_START_LINE means OpenSSL could not find a BEGIN CERTIFICATE + // marker after successfully reading input. That includes such use cases as + // empty input, valid input exhausted by previous extractions, malformed + // input, and valid key-only input without the certificate. We cannot + // distinguish all these outcomes and treat this error as an EOF condition. + if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) { + // consume PEM_R_NO_START_LINE to clean global error queue (if that was + // the only error) and/or to let us check for other errors (otherwise) + (void)ERR_get_error(); + if (!ERR_peek_last_error()) + return nullptr; // EOF without any other errors + } + + ThrowErrors("cannot read a PEM-encoded certificate", savedErrno, Here()); +} + +Security::CertPointer +Ssl::ReadCertificate(const BIO_Pointer &bio) { - assert(bio); - return Security::CertPointer(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); + if (const auto cert = ReadOptionalCertificate(bio)) + return cert; + + // PEM_R_NO_START_LINE + throw TextException("missing a required PEM-encoded certificate", Here()); } bool diff --git a/src/ssl/gadgets.h b/src/ssl/gadgets.h index 3c4de81b07..1785e72bbc 100644 --- a/src/ssl/gadgets.h +++ b/src/ssl/gadgets.h @@ -72,6 +72,16 @@ typedef std::unique_ptr> X509_EXTENSION_Pointer; typedef std::unique_ptr> X509_STORE_CTX_Pointer; + +/// Clear any errors accumulated by OpenSSL in its global storage. +void ForgetErrors(); + +/// Manipulator to report errors accumulated by OpenSSL in its global storage. +/// Each error is reported on a dedicated Debug::Extra line. +/// Nothing is reported if there are no errors. +/// Also clears all reported errors. +std::ostream &ReportAndForgetErrors(std::ostream &); + /** \ingroup SslCrtdSslAPI * Create 1024 bits rsa key. @@ -96,11 +106,9 @@ bool appendCertToMemory(Security::CertPointer const & cert, std::string & buffer */ bool readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Security::PrivateKeyPointer & pkey, char const * bufferToRead); -/** - \ingroup SslCrtdSslAPI - * Read SSL certificate from memory. - */ -bool readCertFromMemory(Security::CertPointer & cert, char const * bufferToRead); +/// Creates and returns a BIO for reading from the given c-string. +/// The returned BIO lifetime must not exceed that of the given c-string! +BIO_Pointer ReadOnlyBioTiedTo(const char *); /** \ingroup SslCrtdSslAPI @@ -114,9 +122,13 @@ void ReadPrivateKeyFromFile(char const * keyFilename, Security::PrivateKeyPointe */ bool OpenCertsFileForReading(BIO_Pointer &bio, const char *filename); -/// reads and returns a certificate using the given OpenSSL BIO -/// \returns a nil pointer on errors (TODO: throw instead) -Security::CertPointer ReadX509Certificate(const BIO_Pointer &); +/// Reads and returns a certificate using the given OpenSSL BIO. +/// Never returns a nil pointer. +Security::CertPointer ReadCertificate(const BIO_Pointer &); + +/// Reads and returns a certificate using the given OpenSSL BIO. +/// \returns a nil pointer if the given BIO is empty or exhausted +Security::CertPointer ReadOptionalCertificate(const BIO_Pointer &); /** \ingroup SslCrtdSslAPI diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 112407723d..873b269d11 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -1116,7 +1116,7 @@ Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list) return false; } - while (auto aCert = ReadX509Certificate(in)) { + while (auto aCert = ReadOptionalCertificate(in)) { static char buffer[2048]; X509_NAME_oneline(X509_get_subject_name(aCert.get()), buffer, sizeof(buffer)); list.insert(std::pair(SBuf(buffer), aCert.release())); diff --git a/src/tests/stub_liberror.cc b/src/tests/stub_liberror.cc index f5f977d2bd..51bbcb2bba 100644 --- a/src/tests/stub_liberror.cc +++ b/src/tests/stub_liberror.cc @@ -13,3 +13,9 @@ std::ostream &operator <<(std::ostream &os, const Error &) STUB_RETVAL(os) ErrorDetail::Pointer MakeNamedErrorDetail(const char *) STUB_RETVAL(ErrorDetail::Pointer()) +#include "error/SysErrorDetail.h" +SBuf SysErrorDetail::Brief(int) STUB_RETVAL(SBuf()) +SBuf SysErrorDetail::brief() const STUB_RETVAL(SBuf()) +SBuf SysErrorDetail::verbose(const HttpRequestPointer &) const STUB_RETVAL(SBuf()) +std::ostream &operator <<(std::ostream &os, ReportSysError) STUB_RETVAL(os) +