diff --git a/build/install_linux_sysroot.py b/build/install_linux_sysroot.py index 97f2c140..4e89837c 100755 --- a/build/install_linux_sysroot.py +++ b/build/install_linux_sysroot.py @@ -29,8 +29,8 @@ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # https://cs.chromium.org/chromium/src/build/linux/sysroot_scripts/sysroots.json SERVER = 'https://commondatastorage.googleapis.com' PATH = 'chrome-linux-sysroot/toolchain' -REVISION = '3c248ba4290a5ad07085b7af07e6785bf1ae5b66' -FILENAME = 'debian_stretch_amd64_sysroot.tar.xz' +REVISION = '43a87bbebccad99325fdcf34166295b121ee15c7' +FILENAME = 'debian_sid_amd64_sysroot.tar.xz' def main(): diff --git a/handler/crash_report_upload_thread.cc b/handler/crash_report_upload_thread.cc index 1e5f1237..b7e445fd 100644 --- a/handler/crash_report_upload_thread.cc +++ b/handler/crash_report_upload_thread.cc @@ -294,6 +294,10 @@ CrashReportUploadThread::UploadResult CrashReportUploadThread::UploadReport( "application/octet-stream"); std::unique_ptr http_transport(HTTPTransport::Create()); + if (!http_transport) { + return UploadResult::kPermanentFailure; + } + HTTPHeaders content_headers; http_multipart_builder.PopulateContentHeaders(&content_headers); for (const auto& content_header : content_headers) { diff --git a/util/BUILD.gn b/util/BUILD.gn index f51f0b9e..fb30636c 100644 --- a/util/BUILD.gn +++ b/util/BUILD.gn @@ -387,7 +387,7 @@ crashpad_static_library("util") { deps = [] - if (crashpad_is_linux || crashpad_is_fuchsia || crashpad_is_android) { + if (crashpad_http_transport_impl == "socket") { sources += [ "net/http_transport_socket.cc" ] if (crashpad_use_boringssl_for_http_transport_socket) { defines += [ "CRASHPAD_USE_BORINGSSL" ] @@ -401,6 +401,8 @@ crashpad_static_library("util") { ] } } + } else if (crashpad_http_transport_impl == "libcurl") { + sources += [ "net/http_transport_libcurl.cc" ] } if (crashpad_is_android) { diff --git a/util/misc/no_cfi_icall.h b/util/misc/no_cfi_icall.h index 25535b40..817dca64 100644 --- a/util/misc/no_cfi_icall.h +++ b/util/misc/no_cfi_icall.h @@ -123,6 +123,9 @@ class NoCfiIcall { //! \param function A pointer to the function to be called. explicit NoCfiIcall(Functor function) : function_(function) {} + //! \see NoCfiIcall + NoCfiIcall() : function_(static_cast(nullptr)) {} + //! \see NoCfiIcall template ::type, + void*>::value>> + void SetPointer(PointerType function) { + function_ = reinterpret_cast(function); + } + //! \brief Calls the function without sanitization by cfi-icall. template decltype(auto) operator()(RunArgs&&... args) const { diff --git a/util/net/http_transport_libcurl.cc b/util/net/http_transport_libcurl.cc new file mode 100644 index 00000000..8993ebca --- /dev/null +++ b/util/net/http_transport_libcurl.cc @@ -0,0 +1,538 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// 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. + +#include "util/net/http_transport.h" + +#include +#include +#include +#include + +#include +#include + +#include "base/check.h" +#include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "base/scoped_generic.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "package.h" +#include "util/misc/no_cfi_icall.h" +#include "util/net/http_body.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +namespace { + +// Crashpad depends on libcurl via dlopen() in order to maximize its tolerance +// of various libcurl flavors and installation configurations. This class serves +// as a linkage table for libcurl procedures. +class Libcurl { + public: + static bool Initialized() { + static bool initialized = Get()->Initialize(); + return initialized; + } + + static void CurlEasyCleanup(CURL* curl) { + return Get()->curl_easy_cleanup_(curl); + } + + static CURL* CurlEasyInit() { return Get()->curl_easy_init_(); } + + static CURLcode CurlEasyPerform(CURL* curl) { + return Get()->curl_easy_perform_(curl); + } + + static const char* CurlEasyStrError(CURLcode code) { + return Get()->curl_easy_strerror_(code); + } + + template + static CURLcode CurlEasyGetInfo(CURL* curl, CURLINFO info, Pointer out) { + return Get()->curl_easy_getinfo_(curl, info, out); + } + + template + static CURLcode CurlEasySetOpt(CURL* curl, CURLoption option, Pointer param) { + return Get()->curl_easy_setopt_(curl, option, param); + } + + static CURLcode CurlGlobalInit(long flags) { + return Get()->curl_global_init_(flags); + } + + static void CurlSlistFreeAll(struct curl_slist* slist) { + return Get()->curl_slist_free_all_(slist); + } + + static struct curl_slist* CurlSlistAppend(struct curl_slist* slist, + const char* data) { + return Get()->curl_slist_append_(slist, data); + } + + static char* CurlVersion() { return Get()->curl_version_(); } + + private: + Libcurl() = default; + ~Libcurl() = delete; + + static Libcurl* Get() { + static Libcurl* instance = new Libcurl(); + return instance; + } + + bool Initialize() { + void* libcurl = []() { + std::vector errors; + for (const auto& lib : { + "libcurl.so", + "libcurl-gnutls.so.4", + "libcurl-nss.so.4", + "libcurl.so.4", + }) { + void* libcurl = dlopen(lib, RTLD_LAZY | RTLD_LOCAL); + if (libcurl) { + return libcurl; + } + errors.emplace_back(dlerror()); + } + for (const auto& message : errors) { + LOG(ERROR) << "dlopen:" << message; + } + return static_cast(nullptr); + }(); + if (!libcurl) { + return false; + } + +#define LINK_OR_RETURN_FALSE(symbol) \ + do { \ + symbol##_.SetPointer(dlsym(libcurl, #symbol)); \ + if (!symbol##_) { \ + LOG(ERROR) << "dlsym:" << dlerror(); \ + return false; \ + } \ + } while (0); + + LINK_OR_RETURN_FALSE(curl_easy_cleanup); + LINK_OR_RETURN_FALSE(curl_easy_init); + LINK_OR_RETURN_FALSE(curl_easy_perform); + LINK_OR_RETURN_FALSE(curl_easy_strerror); + LINK_OR_RETURN_FALSE(curl_easy_getinfo); + LINK_OR_RETURN_FALSE(curl_easy_setopt); + LINK_OR_RETURN_FALSE(curl_global_init); + LINK_OR_RETURN_FALSE(curl_slist_free_all); + LINK_OR_RETURN_FALSE(curl_slist_append); + LINK_OR_RETURN_FALSE(curl_version); + +#undef LINK_OR_RETURN_FALSE + + return true; + } + + NoCfiIcall curl_easy_cleanup_; + NoCfiIcall curl_easy_init_; + NoCfiIcall curl_easy_perform_; + NoCfiIcall curl_easy_strerror_; + NoCfiIcall curl_easy_getinfo_; + NoCfiIcall curl_easy_setopt_; + NoCfiIcall curl_global_init_; + NoCfiIcall curl_slist_free_all_; + NoCfiIcall curl_slist_append_; + NoCfiIcall curl_version_; + + DISALLOW_COPY_AND_ASSIGN(Libcurl); +}; + +std::string UserAgent() { + std::string user_agent = base::StringPrintf( + "%s/%s %s", PACKAGE_NAME, PACKAGE_VERSION, Libcurl::CurlVersion()); + + utsname os; + if (uname(&os) != 0) { + PLOG(WARNING) << "uname"; + } else { + // Match the architecture name that would be used by the kernel, so that the + // strcmp() below can omit the kernel’s architecture name if it’s the same + // as the user process’ architecture. On Linux, these names are normally + // defined in each architecture’s Makefile as UTS_MACHINE, but can be + // overridden in architecture-specific configuration as COMPAT_UTS_MACHINE. + // See linux-4.9.17/arch/*/Makefile and + // linux-4.9.17/arch/*/include/asm/compat.h. In turn, on some systems, these + // names are further overridden or refined in early kernel startup code by + // modifying the string returned by linux-4.9.17/include/linux/utsname.h + // init_utsname() as noted. +#if defined(ARCH_CPU_X86) + // linux-4.9.17/arch/x86/kernel/cpu/bugs.c check_bugs() sets the first digit + // to 4, 5, or 6, but no higher. +#if defined(__i686__) + static constexpr char arch[] = "i686"; +#elif defined(__i586__) + static constexpr char arch[] = "i586"; +#elif defined(__i486__) + static constexpr char arch[] = "i486"; +#else + static constexpr char arch[] = "i386"; +#endif +#elif defined(ARCH_CPU_X86_64) + static constexpr char arch[] = "x86_64"; +#elif defined(ARCH_CPU_ARMEL) + // linux-4.9.17/arch/arm/kernel/setup.c setup_processor() bases the string + // on the ARM processor name and a character identifying little- or + // big-endian. The processor name comes from a definition in + // arch/arm/mm/proc-*.S. +#if defined(__ARM_ARCH_4T__) + static constexpr char arch[] = + "armv4t" +#elif defined(__ARM_ARCH_5TEJ__) + static constexpr char arch[] = + "armv5tej" +#elif defined(__ARM_ARCH_5TE__) + static constexpr char arch[] = + "armv5te" +#elif defined(__ARM_ARCH_5T__) + static constexpr char arch[] = + "armv5t" +#elif defined(__ARM_ARCH_7M__) + static constexpr char arch[] = + "armv7m" +#else + // Most ARM architectures fall into here, including all profile variants of + // armv6, armv7, armv8, with one exception, armv7m, handled above. + // xstr(__ARM_ARCH) will be the architecture revision number, such as 6, 7, + // or 8. +#define xstr(s) str(s) +#define str(s) #s + static constexpr char arch[] = + "armv" xstr(__ARM_ARCH) +#undef str +#undef xstr +#endif +#if defined(ARCH_CPU_LITTLE_ENDIAN) + "l"; +#elif defined(ARCH_CPU_BIG_ENDIAN) + "b"; +#endif +#elif defined(ARCH_CPU_ARM64) + // ARM64 uses aarch64 or aarch64_be as directed by ELF_PLATFORM. See + // linux-4.9.17/arch/arm64/kernel/setup.c setup_arch(). +#if defined(ARCH_CPU_LITTLE_ENDIAN) + static constexpr char arch[] = "aarch64"; +#elif defined(ARCH_CPU_BIG_ENDIAN) + static constexpr char arch[] = "aarch64_be"; +#endif +#else +#error Port +#endif + + user_agent.append( + base::StringPrintf(" %s/%s (%s", os.sysname, os.release, arch)); + if (strcmp(arch, os.machine) != 0) { + user_agent.append(base::StringPrintf("; %s", os.machine)); + } + user_agent.append(1, ')'); + } + + return user_agent; +} + +std::string CurlErrorMessage(CURLcode curl_err, const std::string& base) { + return base::StringPrintf("%s: %s (%d)", + base.c_str(), + Libcurl::CurlEasyStrError(curl_err), + curl_err); +} + +struct ScopedCURLTraits { + static CURL* InvalidValue() { return nullptr; } + static void Free(CURL* curl) { + if (curl) { + Libcurl::CurlEasyCleanup(curl); + } + } +}; +using ScopedCURL = base::ScopedGeneric; + +class CurlSList { + public: + CurlSList() : list_(nullptr) {} + ~CurlSList() { + if (list_) { + Libcurl::CurlSlistFreeAll(list_); + } + } + + curl_slist* get() const { return list_; } + + bool Append(const char* data) { + curl_slist* list = Libcurl::CurlSlistAppend(list_, data); + if (!list_) { + list_ = list; + } + return list != nullptr; + } + + private: + curl_slist* list_; + + DISALLOW_COPY_AND_ASSIGN(CurlSList); +}; + +class ScopedClearString { + public: + explicit ScopedClearString(std::string* string) : string_(string) {} + + ~ScopedClearString() { + if (string_) { + string_->clear(); + } + } + + void Disarm() { string_ = nullptr; } + + private: + std::string* string_; + + DISALLOW_COPY_AND_ASSIGN(ScopedClearString); +}; + +class HTTPTransportLibcurl final : public HTTPTransport { + public: + HTTPTransportLibcurl(); + ~HTTPTransportLibcurl() override; + + // HTTPTransport: + bool ExecuteSynchronously(std::string* response_body) override; + + private: + static size_t ReadRequestBody(char* buffer, + size_t size, + size_t nitems, + void* userdata); + static size_t WriteResponseBody(char* buffer, + size_t size, + size_t nitems, + void* userdata); + + DISALLOW_COPY_AND_ASSIGN(HTTPTransportLibcurl); +}; + +HTTPTransportLibcurl::HTTPTransportLibcurl() : HTTPTransport() {} + +HTTPTransportLibcurl::~HTTPTransportLibcurl() {} + +bool HTTPTransportLibcurl::ExecuteSynchronously(std::string* response_body) { + DCHECK(body_stream()); + + response_body->clear(); + + // curl_easy_init() will do this on the first call if it hasn’t been done yet, + // but not in a thread-safe way as is done here. + static CURLcode curl_global_init_err = []() { + return Libcurl::CurlGlobalInit(CURL_GLOBAL_DEFAULT); + }(); + if (curl_global_init_err != CURLE_OK) { + LOG(ERROR) << CurlErrorMessage(curl_global_init_err, "curl_global_init"); + return false; + } + + CurlSList curl_headers; + ScopedCURL curl(Libcurl::CurlEasyInit()); + if (!curl.get()) { + LOG(ERROR) << "curl_easy_init"; + return false; + } + +// These macros wrap the repetitive “try something, log an error and return +// false on failure” pattern. Macros are convenient because the log messages +// will point to the correct line number, which can help pinpoint a problem when +// there are as many calls to these functions as there are here. +#define TRY_CURL_EASY_SETOPT(curl, option, parameter) \ + do { \ + CURLcode curl_err = \ + Libcurl::CurlEasySetOpt((curl), (option), (parameter)); \ + if (curl_err != CURLE_OK) { \ + LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_setopt"); \ + return false; \ + } \ + } while (false) +#define TRY_CURL_SLIST_APPEND(slist, data) \ + do { \ + if (!(slist).Append(data)) { \ + LOG(ERROR) << "curl_slist_append"; \ + return false; \ + } \ + } while (false) + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_USERAGENT, UserAgent().c_str()); + + // Accept and automatically decode any encoding that libcurl understands. + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_ACCEPT_ENCODING, ""); + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_URL, url().c_str()); + + if (!root_ca_certificate_path().empty()) { + TRY_CURL_EASY_SETOPT( + curl.get(), CURLOPT_CAINFO, root_ca_certificate_path().value().c_str()); + } + + constexpr int kMillisecondsPerSecond = 1E3; + TRY_CURL_EASY_SETOPT(curl.get(), + CURLOPT_TIMEOUT_MS, + static_cast(timeout() * kMillisecondsPerSecond)); + + // If the request body size is known ahead of time, a Content-Length header + // field will be present. Store that to use as CURLOPT_POSTFIELDSIZE_LARGE, + // which will both set the Content-Length field in the request header and + // inform libcurl of the request body size. Otherwise, use Transfer-Encoding: + // chunked, which does not require advance knowledge of the request body size. + bool chunked = true; + size_t content_length; + for (const auto& pair : headers()) { + if (pair.first == kContentLength) { + chunked = !base::StringToSizeT(pair.second, &content_length); + DCHECK(!chunked); + } else { + TRY_CURL_SLIST_APPEND(curl_headers, + (pair.first + ": " + pair.second).c_str()); + } + } + + if (method() == "POST") { + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_POST, 1l); + + // By default when sending a POST request, libcurl includes an “Expect: + // 100-continue” header field. Althogh this header is specified in HTTP/1.1 + // (RFC 2616 §8.2.3, RFC 7231 §5.1.1), even collection servers that claim to + // speak HTTP/1.1 may not respond to it. When sending this header field, + // libcurl will wait for one second for the server to respond with a “100 + // Continue” status before continuing to transmit the request body. This + // delay is avoided by telling libcurl not to send this header field at all. + // The drawback is that certain HTTP error statuses may not be received + // until after substantial amounts of data have been sent to the server. + TRY_CURL_SLIST_APPEND(curl_headers, "Expect:"); + + if (chunked) { + TRY_CURL_SLIST_APPEND(curl_headers, "Transfer-Encoding: chunked"); + } else { + curl_off_t content_length_curl; + if (!AssignIfInRange(&content_length_curl, content_length)) { + LOG(ERROR) << base::StringPrintf("Content-Length %zu too large", + content_length); + return false; + } + TRY_CURL_EASY_SETOPT( + curl.get(), CURLOPT_POSTFIELDSIZE_LARGE, content_length_curl); + } + } else if (method() != "GET") { + // Untested. + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_CUSTOMREQUEST, method().c_str()); + } + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get()); + + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READFUNCTION, ReadRequestBody); + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_READDATA, this); + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEFUNCTION, WriteResponseBody); + TRY_CURL_EASY_SETOPT(curl.get(), CURLOPT_WRITEDATA, response_body); + +#undef TRY_CURL_EASY_SETOPT +#undef TRY_CURL_SLIST_APPEND + + // If a partial response body is received and then a failure occurs, ensure + // that response_body is cleared. + ScopedClearString clear_response_body(response_body); + + // Do it. + CURLcode curl_err = Libcurl::CurlEasyPerform(curl.get()); + if (curl_err != CURLE_OK) { + LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_perform"); + return false; + } + + long status; + curl_err = + Libcurl::CurlEasyGetInfo(curl.get(), CURLINFO_RESPONSE_CODE, &status); + if (curl_err != CURLE_OK) { + LOG(ERROR) << CurlErrorMessage(curl_err, "curl_easy_getinfo"); + return false; + } + + if (status != 200) { + LOG(ERROR) << base::StringPrintf("HTTP status %ld", status); + return false; + } + + // The response body is complete. Don’t clear it. + clear_response_body.Disarm(); + + return true; +} + +// static +size_t HTTPTransportLibcurl::ReadRequestBody(char* buffer, + size_t size, + size_t nitems, + void* userdata) { + HTTPTransportLibcurl* self = + reinterpret_cast(userdata); + + // This libcurl callback mimics the silly stdio-style fread() interface: size + // and nitems have been separated and must be multiplied. + base::CheckedNumeric checked_len = base::CheckMul(size, nitems); + size_t len = checked_len.ValueOrDefault(std::numeric_limits::max()); + + // Limit the read to what can be expressed in a FileOperationResult. + len = std::min( + len, + static_cast(std::numeric_limits::max())); + + FileOperationResult bytes_read = self->body_stream()->GetBytesBuffer( + reinterpret_cast(buffer), len); + if (bytes_read < 0) { + return CURL_READFUNC_ABORT; + } + + return bytes_read; +} + +// static +size_t HTTPTransportLibcurl::WriteResponseBody(char* buffer, + size_t size, + size_t nitems, + void* userdata) { + std::string* response_body = reinterpret_cast(userdata); + + // This libcurl callback mimics the silly stdio-style fread() interface: size + // and nitems have been separated and must be multiplied. + base::CheckedNumeric checked_len = base::CheckMul(size, nitems); + size_t len = checked_len.ValueOrDefault(std::numeric_limits::max()); + + response_body->append(buffer, len); + return len; +} + +} // namespace + +// static +std::unique_ptr HTTPTransport::Create() { + return std::unique_ptr( + Libcurl::Initialized() ? new HTTPTransportLibcurl() : nullptr); +} + +} // namespace crashpad diff --git a/util/net/tls.gni b/util/net/tls.gni index eb968450..343e9dbb 100644 --- a/util/net/tls.gni +++ b/util/net/tls.gni @@ -15,6 +15,14 @@ import("../../build/crashpad_buildconfig.gni") declare_args() { + if (crashpad_is_fuchsia || crashpad_is_android) { + crashpad_http_transport_impl = "socket" + } else if (crashpad_is_linux) { + crashpad_http_transport_impl = "libcurl" + } else { + crashpad_http_transport_impl = "" + } + # TODO(scottmg): https://crbug.com/crashpad/266 fuchsia:DX-690: BoringSSL # was removed from the Fuchsia SDK. Re-enable it when we have a way to acquire # a BoringSSL lib again.