123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- // 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 <curl/curl.h>
- #include <dlfcn.h>
- #include <string.h>
- #include <sys/utsname.h>
- #include <algorithm>
- #include <limits>
- #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:
- Libcurl(const Libcurl&) = delete;
- Libcurl& operator=(const Libcurl&) = delete;
- 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 <typename Pointer>
- static CURLcode CurlEasyGetInfo(CURL* curl, CURLINFO info, Pointer out) {
- return Get()->curl_easy_getinfo_(curl, info, out);
- }
- template <typename Pointer>
- 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<std::string> 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<void*>(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<decltype(curl_easy_cleanup)*> curl_easy_cleanup_;
- NoCfiIcall<decltype(curl_easy_init)*> curl_easy_init_;
- NoCfiIcall<decltype(curl_easy_perform)*> curl_easy_perform_;
- NoCfiIcall<decltype(curl_easy_strerror)*> curl_easy_strerror_;
- NoCfiIcall<decltype(curl_easy_getinfo)*> curl_easy_getinfo_;
- NoCfiIcall<decltype(curl_easy_setopt)*> curl_easy_setopt_;
- NoCfiIcall<decltype(curl_global_init)*> curl_global_init_;
- NoCfiIcall<decltype(curl_slist_free_all)*> curl_slist_free_all_;
- NoCfiIcall<decltype(curl_slist_append)*> curl_slist_append_;
- NoCfiIcall<decltype(curl_version)*> curl_version_;
- };
- 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
- #elif defined(ARCH_CPU_RISCV64)
- static constexpr char arch[] = "riscv64";
- #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<CURL*, ScopedCURLTraits>;
- class CurlSList {
- public:
- CurlSList() : list_(nullptr) {}
- CurlSList(const CurlSList&) = delete;
- CurlSList& operator=(const CurlSList&) = delete;
- ~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_;
- };
- class ScopedClearString {
- public:
- explicit ScopedClearString(std::string* string) : string_(string) {}
- ScopedClearString(const ScopedClearString&) = delete;
- ScopedClearString& operator=(const ScopedClearString&) = delete;
- ~ScopedClearString() {
- if (string_) {
- string_->clear();
- }
- }
- void Disarm() { string_ = nullptr; }
- private:
- std::string* string_;
- };
- class HTTPTransportLibcurl final : public HTTPTransport {
- public:
- HTTPTransportLibcurl();
- HTTPTransportLibcurl(const HTTPTransportLibcurl&) = delete;
- HTTPTransportLibcurl& operator=(const HTTPTransportLibcurl&) = delete;
- ~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);
- };
- 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<long>(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<HTTPTransportLibcurl*>(userdata);
- // This libcurl callback mimics the silly stdio-style fread() interface: size
- // and nitems have been separated and must be multiplied.
- base::CheckedNumeric<size_t> checked_len = base::CheckMul(size, nitems);
- size_t len = checked_len.ValueOrDefault(std::numeric_limits<size_t>::max());
- // Limit the read to what can be expressed in a FileOperationResult.
- len = std::min(
- len,
- static_cast<size_t>(std::numeric_limits<FileOperationResult>::max()));
- FileOperationResult bytes_read = self->body_stream()->GetBytesBuffer(
- reinterpret_cast<uint8_t*>(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<std::string*>(userdata);
- // This libcurl callback mimics the silly stdio-style fread() interface: size
- // and nitems have been separated and must be multiplied.
- base::CheckedNumeric<size_t> checked_len = base::CheckMul(size, nitems);
- size_t len = checked_len.ValueOrDefault(std::numeric_limits<size_t>::max());
- response_body->append(buffer, len);
- return len;
- }
- } // namespace
- // static
- std::unique_ptr<HTTPTransport> HTTPTransport::Create() {
- return std::unique_ptr<HTTPTransport>(
- Libcurl::Initialized() ? new HTTPTransportLibcurl() : nullptr);
- }
- } // namespace crashpad
|