crashpad/util/net/http_transport_libcurl.cc
Thomas Gales 4f5dd67229 [riscv] Add RISC-V Linux support
Only RV64GC is supported.

Bug: fuchsia:127655

Tested: `python build/run_tests.py` on RISC-V emulator
Tested: Created minidump via self-induced crash on RISC-V emulator,
ran through Breakpad stackwalker

Change-Id: I713797cd623b0a758269048e01696cbce502ca6c
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4581050
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2023-06-12 21:13:24 +00:00

551 lines
18 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2017 The Crashpad Authors
//
// 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 kernels architecture name if its the same
// as the user process architecture. On Linux, these names are normally
// defined in each architectures 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 hasnt 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. Dont 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) {
#if defined(MEMORY_SANITIZER)
// Work around an MSAN false-positive in passing `userdata`.
__msan_unpoison(&userdata, sizeof(userdata));
#endif
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