Index: compiler-rt/lib/fuzzer/CMakeLists.txt =================================================================== --- compiler-rt/lib/fuzzer/CMakeLists.txt +++ compiler-rt/lib/fuzzer/CMakeLists.txt @@ -208,6 +208,8 @@ OBJECT_LIBS RTfuzzer_base RTfuzzer_remote) +add_subdirectory(ipc) + if(COMPILER_RT_INCLUDE_TESTS) add_subdirectory(tests) endif() Index: compiler-rt/lib/fuzzer/ipc/CMakeLists.txt =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/ipc/CMakeLists.txt @@ -0,0 +1,9 @@ +include(CompilerRTCompile) + +add_libfuzzer_objects(RTfuzzer_ipc_base + SOURCES IPCLinux.cpp + HEADERS IPCLinux.h) + +add_libfuzzer_objects(RTfuzzer_ipc + SOURCES IPCQuickExit.cpp + HEADERS IPCQuickExit.h) Index: compiler-rt/lib/fuzzer/ipc/IPCLinux.h =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/ipc/IPCLinux.h @@ -0,0 +1,266 @@ +//===- IPCLinux.h -----------------------------------------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Defines messages, connections and servers for communicating across processes +// in Linux. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_IPC_LINUX_H +#define LLVM_FUZZER_IPC_LINUX_H + +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX + +#include "FuzzerLock.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FuzzerUtil.h" + +namespace fuzzer { +namespace ipc { + +// Helper class that makes blocking reads to a file descriptor interruptible by +// specific events. +class PreemptibleFd final { +public: + enum Event : uint64_t { + kReady = 1, // Indicates data is available to be read. + kStop, // Break if receiving in a loop. + kError, + }; + + PreemptibleFd(); + ~PreemptibleFd(); + + int Fd() EXCLUDES(FdsMutex); + void Open(int NewFd) EXCLUDES(FdsMutex); + void WriteEvent(Event E) EXCLUDES(FdsMutex); + Event ReadEvent() EXCLUDES(FdsMutex); + bool Close() EXCLUDES(FdsMutex); + +private: + void WriteEventLocked(Event E) REQUIRES(FdsMutex); + + static constexpr nfds_t kNumPollFds = 2; + std::mutex FdsMutex; + struct pollfd PollFds[kNumPollFds] GUARDED_BY(FdsMutex); + + FUZZER_NO_COPY_OR_MOVE(PreemptibleFd); +}; + +// Helper struct for tracking memory regions shared between processes. +struct SharedMemoryRegion { + void *Mapped; + const void *Addr; + size_t Len; +}; + +// Forward declaration. +class ConnectionServer; + +// Represents a one endpoint of connection between a client and server process. +// The same class is used for both endpoints, the only difference is the method +// used to make the connction, i.e. Accept vs Connect. +class Connection { +public: + friend ConnectionServer; + + // Constant used to acknowledge a recieved message. + static constexpr uint8_t kAcknowledgement = 1; + + virtual ~Connection(); + + // Returns the owning server for server-side endpoints, and null for client- + // side endpoints. + ConnectionServer *GetServer(); + + // Sets up this instance as a server-side endpoint of a connection. This + // object must have been constructed using the "server" constructor, below. + bool Accept(int Fd); + + // Sets up this instance as a client-side endpoint of a connection. This + // object must have been constructed using the "client" constructor, below. + bool Connect(const char *EnvVarName); + + // Both endpoints return the *client's* PID, which the server uses to identify + // this connection. + unsigned long GetPid() const { return PID; } + + // Protocol authors can override this method to exchanges handshake messages + // with other end of the connection. Returns true if connection established. + virtual bool DoHandshake() { return true; } + + // Invoked when the underlying socket closes, e.g. when Reset() is called. + virtual void OnClose() {} + +protected: + // Client constructor. + Connection(); + + // Server constructor. + explicit Connection(ConnectionServer *S); + + // PIDs are derived from the process, but unit tests are allowed to override. + void SetPid(unsigned long NewPID) { PID = NewPID; } + + // Derived classes may get a reference to the connection mutex. The caller + // should only use this to create a UniqueLock or std::lock_guard! + std::mutex &GetRxMutex() RETURN_CAPABILITY(RxMutex) { return RxMutex; } + std::mutex &GetTxMutex() RETURN_CAPABILITY(TxMutex) { return TxMutex; } + + // Starts the ReceiverThread. + void StartReceiving() EXCLUDES(TxMutex); + + // Receive and handle a series of messages by repeatedly calling + // DispatchOneLocked until it returns false or StopReceiving is called, if + // running on the ReceiverThread. + void DispatchLoop(); + + // Handle recevied messages according to the details of the protocol. This + // method should return true unless a connection-ending condition is + // encountered, e.g. a protocol error or shutdown request. + virtual bool DispatchOneLocked() REQUIRES(RxMutex) = 0; + + // Protocol messages that happen infrequently (e.g. only on startup or error) + // can be simplified by making them synchronous: The sender can wait for an + // acknowledgement message sent back by the recipient. + void WaitForAcknowledgement() EXCLUDES(Mutex, RxMutex, TxMutex); + void HandleAcknowledgement() EXCLUDES(Mutex); + void Acknowledge() EXCLUDES(GetTxMutex()); + + // Stops the ReceiverThread. + void StopReceiving() EXCLUDES(RxMutex); + + // These methods send data to the underlying socket. They are thread-safe. + bool SendLocked(const void *Buf, size_t BufLen, int AncillaryFd = -1) + REQUIRES(TxMutex); + bool ReceiveLocked(void *Buf, size_t BufLen, int *AncillaryFd = nullptr) + REQUIRES(RxMutex); + + template bool SendLocked(const T &Value) REQUIRES(TxMutex) { + static_assert(std::is_trivially_copyable::value, + "type is not is not trivially copyable"); + return SendLocked(&Value, sizeof(T), -1); + } + template bool ReceiveLocked(T *Value) REQUIRES(RxMutex) { + static_assert(std::is_trivially_copyable::value, + "type is not is not trivially copyable"); + return ReceiveLocked(Value, sizeof(T), nullptr); + } + + bool SendStringLocked(const char *Str) REQUIRES(TxMutex); + bool ReceiveStringLocked(char *Buf, size_t BufLen) REQUIRES(RxMutex); + + bool SendMemoryLocked(const void *Addr, size_t Len, bool Updatable) + REQUIRES(TxMutex); + bool ReceiveMemoryLocked(void **Addr, size_t *Len) REQUIRES(RxMutex); + + // Updates regions of memory shared via SendMemoryLocked with Updatable=true. + // Regions which are not marked updatable are "single-shot" and can be safely + // discard by the sender after sending. + void UpdateSharedMemory(); + + // Closes the underlying file descriptor. This method may be called from the + // ReceiverThread and will eventually lead to a call to Reset on another + // thread. + void Close(); + + // Reset the connection, i.e. disconnect. Blocks until the ReceiverThread has + // stopped to clean up shared resources. It MUST NOT be called from that + // thread. + void Reset(); + +private: + unsigned long PID; + PreemptibleFd Preemptible; + std::thread ReceiverThread; + + std::mutex Mutex; + ConnectionServer *Server GUARDED_BY(Mutex); + std::vector SharedMemory GUARDED_BY(Mutex); + size_t SharedMemoryCount GUARDED_BY(Mutex) = 0; + + // Represents messages transferred between processes. + struct Message { + struct msghdr Header; + struct iovec Data; + alignas(struct cmsghdr) uint8_t Control[CMSG_SPACE(sizeof(int))]; + }; + std::mutex RxMutex; + Message RxMsg GUARDED_BY(RxMutex); + std::mutex TxMutex; + Message TxMsg GUARDED_BY(TxMutex); + + uint64_t OutstandingAcks GUARDED_BY(Mutex) = 0; + std::condition_variable Acknowledgement; + + FUZZER_NO_COPY_OR_MOVE(Connection); +}; + +// This class listens for new connections and manages the server-side endpoints. +// It uses the value of an environment variable as its listening address, so it +// is typically a singleton. +class ConnectionServer { +public: + friend Connection; + virtual ~ConnectionServer(); + + void AddConnection(int Fd) EXCLUDES(ConnectionsMutex); + std::shared_ptr GetConnection(unsigned long PID) + EXCLUDES(ConnectionsMutex); + void RemoveConnection(Connection *C) EXCLUDES(ConnectionsMutex); + +protected: + ConnectionServer(); + + // Manage the server lifecycle. The EnvVarName refers to the name of an + // environment variable containing the path at which the server should create + // a listening Unix domain socket. Clients should use this same environment + // variable to find and connect to the server. If the environment variable is + // unset, this will generate a socket path in a temporary location. + void Start(const char *EnvVarName); + virtual void Stop(); + + // Factory method for server-side endpoints of Connection subclasses provided + // by derived classes. Implementations of this method MUST use the server-side + // constructor. In particular, they MUST provide themselves as an argument to + // the constructor. + virtual std::unique_ptr CreateConnection() = 0; + + // Can be overridden to notify a derived when a connection is made. + virtual void OnConnection(Connection *C) {} + +private: + PreemptibleFd Preemptible; + + std::string SunPath; + std::thread AcceptThread; + std::vector> Removed; + + std::mutex ConnectionsMutex; + using ConnectionMap = + std::unordered_map>; + ConnectionMap Connections GUARDED_BY(ConnectionsMutex); + bool Running GUARDED_BY(ConnectionsMutex); + + FUZZER_NO_COPY_OR_MOVE(ConnectionServer); +}; + +} // namespace ipc +} // namespace fuzzer + +#endif // LIBFUZZER_LINUX +#endif // LLVM_FUZZER_IPC_LINUX_H Index: compiler-rt/lib/fuzzer/ipc/IPCLinux.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/ipc/IPCLinux.cpp @@ -0,0 +1,577 @@ +//===- IPCLinux.cpp -------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// General purpose Linux IPC. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX + +#include "IPCLinux.h" +#include "FuzzerUtil.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "IPCQuickExit.h" + +namespace fuzzer { +namespace ipc { + +const uint8_t Connection::kAcknowledgement; + +namespace { + +// Helper functions to print fatal errors and exit. These avoid using +// fuzzer::Printf as they may be called before OutputFile has been set. + +__attribute__((noreturn)) void Fatalf(const char *Fmt, ...) { + fprintf(stderr, "==%d== ERROR: IPC: ", getpid()); + va_list ap; + va_start(ap, Fmt); + vfprintf(stderr, Fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + _Exit(1); +} + +__attribute__((noreturn)) void PError(const char *Func, + const char *Arg = nullptr) { + if (Arg) + Fatalf("%s(%s): %s.", Func, Arg, strerror(errno)); + else + Fatalf("%s: %s.", Func, strerror(errno)); +} + +// Helper functions for Unix domain sockets +void SetAddress(const char *EnvVarName, struct sockaddr_un *UnixAddr, + bool Generate) { + memset(UnixAddr, 0, sizeof(*UnixAddr)); + UnixAddr->sun_family = AF_UNIX; + char *SunPath = getenv(EnvVarName); + if (SunPath) + snprintf(UnixAddr->sun_path, sizeof(UnixAddr->sun_path), "%s", SunPath); + else if (!Generate) + Fatalf("'%s' environment variable is not set.\n", EnvVarName); + else { + char TmpPath[256]; + char *XdgRuntimeDir = getenv("XDG_RUNTIME_DIR"); + // Prefer $XDG_RUNTIME_DIR, fallback to /tmp + if (XdgRuntimeDir) + snprintf(TmpPath, sizeof(TmpPath), "%s/fuzzer.ipc.XXXXXX", XdgRuntimeDir); + else + snprintf(TmpPath, sizeof(TmpPath), "/tmp/fuzzer.ipc.XXXXXX"); + // We only need a decent name, and do not care about filesystem races. + // Create and discard a secure tempfile, then reuse its name. + int TmpFd = mkstemp(TmpPath); + if (TmpFd == -1) + PError("mkstemp", TmpPath); + if (close(TmpFd) == -1) + PError("close", TmpPath); + if (setenv(EnvVarName, TmpPath, /* overwrite =*/1) == -1) + PError("setenv", EnvVarName); + snprintf(UnixAddr->sun_path, sizeof(UnixAddr->sun_path), "%s", TmpPath); + } +} + +struct sockaddr *Address(struct sockaddr_un *UnixAddr) { + return reinterpret_cast(UnixAddr); +} + +socklen_t Length(const struct sockaddr_un &UnixAddr) { + return sizeof(UnixAddr.sun_family) + strlen(UnixAddr.sun_path); +} + +} // namespace + +// fuzzer::ipc::PreemptibleFd methods + +#define RETRY_ON_EINTR(R, X) \ + do { \ + R = (X); \ + } while (R == -1 && errno == EINTR) + +PreemptibleFd::PreemptibleFd() { + { + std::lock_guard Lock(FdsMutex); + for (size_t i = 0; i < kNumPollFds; ++i) { + PollFds[i].fd = -1; + PollFds[i].events = POLLIN; + PollFds[i].revents = 0; + } + } +} + +PreemptibleFd::~PreemptibleFd() { Close(); } + +int PreemptibleFd::Fd() { + std::lock_guard Lock(FdsMutex); + return PollFds[0].fd; +} + +void PreemptibleFd::Open(int NewFd) { + std::lock_guard Lock(FdsMutex); + for (size_t i = 0; i < kNumPollFds; ++i) + assert(PollFds[i].fd == -1); + assert(NewFd != -1); + PollFds[0].fd = NewFd; + PollFds[1].fd = eventfd(0, 0); + if (PollFds[1].fd == -1) + PError("eventfd"); +} + +void PreemptibleFd::WriteEvent(Event E) { + std::lock_guard Lock(FdsMutex); + WriteEventLocked(E); +} + +void PreemptibleFd::WriteEventLocked(Event E) { + if (PollFds[1].fd != -1) { + ssize_t N; + RETRY_ON_EINTR(N, write(PollFds[1].fd, &E, sizeof(E))); + if (N < 0) + PError("write"); + } +} + +PreemptibleFd::Event PreemptibleFd::ReadEvent() { + struct pollfd *pPollFds; + { + std::lock_guard Lock(FdsMutex); + for (size_t i = 0; i < kNumPollFds; ++i) + if (PollFds[i].fd == -1) + return kError; + pPollFds = PollFds; + }; + int N; + RETRY_ON_EINTR(N, poll(pPollFds, kNumPollFds, -1)); + if (N < 0) + PError("poll"); + { + std::lock_guard Lock(FdsMutex); + Event E; + if (PollFds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) + return kError; + else if (PollFds[0].revents & POLLIN) + return kReady; + else if ((PollFds[1].revents & POLLIN) == 0) + return kError; + else if (read(PollFds[1].fd, &E, sizeof(E)) == -1) + return kError; + else + return E; + } +} + +bool PreemptibleFd::Close() { + std::lock_guard Lock(FdsMutex); + bool Closed = false; + for (size_t i = 0; i < kNumPollFds; ++i) { + if (PollFds[i].fd != -1) { + close(PollFds[0].fd); + PollFds[i].fd = -1; + Closed = true; + } + } + return Closed; +} + +// fuzzer::ipc::ConnectionServer methods + +ConnectionServer::ConnectionServer() {} + +ConnectionServer::~ConnectionServer() { + std::lock_guard Lock(ConnectionsMutex); + assert(!AcceptThread.joinable()); + assert(Connections.empty()); + assert(Removed.empty()); +} + +void ConnectionServer::Start(const char *EnvVarName) { + std::lock_guard Lock(ConnectionsMutex); + assert(!AcceptThread.joinable()); + struct sockaddr_un UnixAddr; + SetAddress(EnvVarName, &UnixAddr, /* Generate */ true); + SunPath = UnixAddr.sun_path; + int Fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (Fd == -1) + PError("socket"); + if (unlink(SunPath.c_str()) == -1 && errno != ENOENT) + PError("unlink", SunPath.c_str()); + if (bind(Fd, Address(&UnixAddr), Length(UnixAddr)) == -1) + PError("bind", SunPath.c_str()); + if (listen(Fd, 32) == -1) + PError("listen"); + Preemptible.Open(Fd); + Running = true; + AcceptThread = std::thread([this]() { + while (true) { + auto Event = Preemptible.ReadEvent(); + switch (Event) { + case PreemptibleFd::kReady: { + int ClientFd = accept(Preemptible.Fd(), nullptr, nullptr); + if (ClientFd == -1) + PError("accept"); + auto C = CreateConnection(); + assert(C->GetServer() == this); + if (!C->Accept(ClientFd)) + continue; + auto *Client = C.get(); + auto PID = Client->GetPid(); + std::shared_ptr Prev; + { + std::lock_guard Lock(ConnectionsMutex); + auto I = Connections.find(PID); + if (I != Connections.end()) { + Prev = I->second; + } + Connections[PID] = std::move(C); + } + Prev.reset(); + Client->StartReceiving(); + OnConnection(Client); + break; + } + case PreemptibleFd::kStop: { + { + std::lock_guard Lock(ConnectionsMutex); + for (auto R : Removed) + R->Reset(); + Removed.clear(); + if (Running) + continue; + for (auto &C : Connections) + C.second->Reset(); + Connections.clear(); + } + if (unlink(SunPath.c_str()) == -1 && errno != ENOENT) + PError("unlink", SunPath.c_str()); + return; + } + case PreemptibleFd::kError: + Fatalf("Protocol error"); + } + } + }); +} + +std::shared_ptr ConnectionServer::GetConnection(unsigned long PID) { + std::lock_guard Lock(ConnectionsMutex); + auto C = Connections.find(PID); + return C == Connections.end() ? nullptr : C->second; +} + +void ConnectionServer::RemoveConnection(Connection *C) { + { + std::lock_guard Lock(ConnectionsMutex); + auto I = Connections.find(C->GetPid()); + if (I == Connections.end() || I->second.get() != C) + return; + Removed.push_back(I->second); + Connections.erase(I); + } + Preemptible.WriteEvent(PreemptibleFd::kStop); +} + +void ConnectionServer::Stop() { + std::thread Tmp; + { + std::lock_guard Lock(ConnectionsMutex); + if (!AcceptThread.joinable()) { + return; + } + Tmp = std::move(AcceptThread); + Running = false; + } + Preemptible.WriteEvent(PreemptibleFd::kStop); + Tmp.join(); + Preemptible.Close(); +} + +// fuzzer::ipc::Connection methods + +Connection::Connection() : Connection(nullptr) { PID = fuzzer::GetPid(); } + +Connection::Connection(ConnectionServer *S) : PID(0), Server(S) { + memset(&TxMsg.Header, 0, sizeof(TxMsg.Header)); + memset(TxMsg.Control, 0, sizeof(TxMsg.Control)); + TxMsg.Header.msg_iov = &TxMsg.Data; + TxMsg.Header.msg_iovlen = 1; + + memset(&RxMsg.Header, 0, sizeof(RxMsg.Header)); + memset(RxMsg.Control, 0, sizeof(RxMsg.Control)); + RxMsg.Header.msg_iov = &RxMsg.Data; + RxMsg.Header.msg_iovlen = 1; +} + +Connection::~Connection() { + assert(!ReceiverThread.joinable()); + assert(SharedMemory.empty()); + assert(OutstandingAcks == 0); +} + +ConnectionServer *Connection::GetServer() { + std::lock_guard Lock(Mutex); + return Server; +} + +bool Connection::Accept(int Fd) { + assert(GetServer() != nullptr); + Preemptible.Open(Fd); + std::lock_guard RxLock(RxMutex); + return ReceiveLocked(&PID); +} + +void Connection::Close() { + if (Preemptible.Close()) + OnClose(); +} + +void Connection::Reset() { + StopReceiving(); + Close(); + std::lock_guard Lock(Mutex); + for (auto &M : SharedMemory) + if (munmap(M.Mapped, M.Len) == -1) + PError("munmap"); + SharedMemory.clear(); +} + +bool Connection::Connect(const char *EnvVarName) { + assert(GetServer() == nullptr); + Reset(); + struct sockaddr_un UnixAddr; + SetAddress(EnvVarName, &UnixAddr, /* Generate */ false); + int Fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (Fd == -1) + PError("socket"); + if (connect(Fd, Address(&UnixAddr), Length(UnixAddr)) == -1) + PError("connect", UnixAddr.sun_path); + Preemptible.Open(Fd); + { + std::lock_guard TxLock(TxMutex); + if (!SendLocked(PID)) + return false; + } + { + std::lock_guard RxLock(RxMutex); + if (!ReceiveLocked(&PID)) + return false; + } + StartReceiving(); + return true; +} + +void Connection::StartReceiving() { + if (GetServer() != nullptr) { + std::lock_guard TxLock(TxMutex); + SendLocked(PID); + } + DoHandshake(); + ReceiverThread = std::thread([this]() { DispatchLoop(); }); +} + +void Connection::DispatchLoop() { + for (bool Ok = true; Ok;) { + switch (Preemptible.ReadEvent()) { + case PreemptibleFd::kReady: { + std::lock_guard RxLock(RxMutex); + Ok = DispatchOneLocked(); + break; + } + case PreemptibleFd::kStop: + return; + case PreemptibleFd::kError: + Ok = false; + break; + } + } + auto *S = GetServer(); + if (S) + S->RemoveConnection(this); + else + LLVMFuzzerIPCQuickExit(1); +} + +void Connection::WaitForAcknowledgement() { + UniqueLock Lock(Mutex); + while (OutstandingAcks == 0) + Acknowledgement.wait(Lock.get()); + OutstandingAcks--; +} + +void Connection::HandleAcknowledgement() { + { + std::lock_guard Lock(Mutex); + OutstandingAcks++; + } + Acknowledgement.notify_one(); +} + +void Connection::Acknowledge() { + std::lock_guard TxLock(GetTxMutex()); + SendLocked(kAcknowledgement); +} + +void Connection::StopReceiving() { + std::thread Tmp; + { + std::lock_guard Lock(Mutex); + if (!ReceiverThread.joinable()) + return; + Tmp = std::move(ReceiverThread); + } + Preemptible.WriteEvent(PreemptibleFd::kStop); + Tmp.join(); +} + +bool Connection::SendLocked(const void *Buf, size_t BufLen, int AncillaryFd) { + assert(Buf != nullptr); + assert(BufLen != 0); + int Fd = Preemptible.Fd(); + if (Fd == -1) + return false; + TxMsg.Data.iov_base = const_cast(Buf); + TxMsg.Data.iov_len = BufLen; + if (AncillaryFd != -1) { + TxMsg.Header.msg_control = TxMsg.Control; + TxMsg.Header.msg_controllen = sizeof(TxMsg.Control); + struct cmsghdr *CMsgHdr = CMSG_FIRSTHDR(&TxMsg.Header); + CMsgHdr->cmsg_len = CMSG_LEN(sizeof(int)); + CMsgHdr->cmsg_level = SOL_SOCKET; + CMsgHdr->cmsg_type = SCM_RIGHTS; + *((int *)CMSG_DATA(CMsgHdr)) = AncillaryFd; + } else { + TxMsg.Header.msg_control = nullptr; + TxMsg.Header.msg_controllen = 0; + } + ssize_t N; + RETRY_ON_EINTR(N, sendmsg(Fd, &TxMsg.Header, MSG_NOSIGNAL)); + if (N > 0) + return true; + else if (N == 0 || errno == EPIPE || errno == ECONNRESET) + return false; + else + PError("sendmsg"); +} + +bool Connection::SendMemoryLocked(const void *Addr, size_t Len, + bool Updatable) { + assert(Addr != nullptr); + assert(Len != 0); + char Name[32]; + { + std::lock_guard Lock(Mutex); + snprintf(Name, sizeof(Name), "fuzzer.ipc.%lu-%zu", GetPid(), + SharedMemoryCount++); + } + int Fd = memfd_create(Name, MFD_ALLOW_SEALING); + if (Fd == -1) + PError("memfd_create"); + if (ftruncate(Fd, Len) == -1) + PError("ftruncate"); + if (fcntl(Fd, F_ADD_SEALS, F_SEAL_SHRINK) == -1 || + fcntl(Fd, F_ADD_SEALS, F_SEAL_GROW) == -1) { + PError("fcntl"); + exit(1); + } + void *Mapped = mmap(nullptr, Len, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0); + if (Mapped == MAP_FAILED) + PError("mmap"); + memcpy(Mapped, Addr, Len); + if (!SendLocked(&Len, sizeof(Len), Fd)) + return false; + close(Fd); + if (Updatable) { + std::lock_guard Lock(Mutex); + SharedMemory.push_back({Mapped, Addr, Len}); + } else if (munmap(Mapped, Len) == -1) { + PError("munmap"); + } + return true; +} + +bool Connection::SendStringLocked(const char *Str) { + assert(Str != nullptr); + size_t Len = strlen(Str); + SendLocked(Len); + return Len == 0 || SendLocked(Str, Len, -1); +} + +bool Connection::ReceiveLocked(void *Buf, size_t BufLen, int *AncillaryFd) { + assert(Buf && BufLen); + int Fd = Preemptible.Fd(); + if (Fd == -1) + return false; + if (AncillaryFd) { + RxMsg.Header.msg_control = RxMsg.Control; + RxMsg.Header.msg_controllen = sizeof(RxMsg.Control); + } else { + RxMsg.Header.msg_control = nullptr; + RxMsg.Header.msg_controllen = 0; + } + RxMsg.Data.iov_base = Buf; + RxMsg.Data.iov_len = BufLen; + ssize_t N; + RETRY_ON_EINTR(N, recvmsg(Fd, &RxMsg.Header, 0)); + if (N <= 0) + return false; + if (!AncillaryFd) + return true; + struct cmsghdr *CMsgHdr = CMSG_FIRSTHDR(&RxMsg.Header); + if (CMsgHdr) + *AncillaryFd = *((int *)CMSG_DATA(CMsgHdr)); + else + *AncillaryFd = -1; + return true; +} + +bool Connection::ReceiveMemoryLocked(void **Addr, size_t *Len) { + int Fd; + if (!ReceiveLocked(Len, sizeof(*Len), &Fd)) + return false; + *Addr = mmap(nullptr, *Len, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0); + if (*Addr == MAP_FAILED) + PError("mmap"); + close(Fd); + return true; +} + +bool Connection::ReceiveStringLocked(char *Buf, size_t BufLen) { + assert(Buf != nullptr); + assert(BufLen != 0); + size_t Len; + if (!ReceiveLocked(&Len)) + return false; + if (Len < BufLen) { + Buf[Len] = '\0'; + return Len == 0 || ReceiveLocked(Buf, Len, nullptr); + } else { + std::vector Tmp(Len * 2, 0); + if (!ReceiveLocked(&Tmp[0], Len, nullptr)) + return false; + snprintf(Buf, BufLen, "%s", &Tmp[0]); + return true; + } +} + +void Connection::UpdateSharedMemory() { + std::lock_guard Lock(Mutex); + for (auto &M : SharedMemory) + memcpy(M.Mapped, M.Addr, M.Len); +} + +} // namespace ipc +} // namespace fuzzer + +#endif // LIBFUZZER_LINUX Index: compiler-rt/lib/fuzzer/ipc/IPCQuickExit.h =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/ipc/IPCQuickExit.h @@ -0,0 +1,20 @@ +//===- IPCQuickExit.h -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Quick exit interface. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" + +extern "C" { + +// Exit the process right away. The consumer of this code must provide an +// implementation of this method. In some cases, it may be desirable to NOT exit +// at all, e.g. unit tests. +ATTRIBUTE_INTERFACE void LLVMFuzzerIPCQuickExit(int ExitCode); + +} // extern "C" Index: compiler-rt/lib/fuzzer/ipc/IPCQuickExit.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/ipc/IPCQuickExit.cpp @@ -0,0 +1,14 @@ +//===- IPCQuickExit.cpp ---------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// A function to exit immediately, which can be excluded from unit tests. +//===----------------------------------------------------------------------===// + +#include "IPCQuickExit.h" +#include + +extern "C" void LLVMFuzzerIPCQuickExit(int ExitCode) { _Exit(ExitCode); } Index: compiler-rt/lib/fuzzer/tests/CMakeLists.txt =================================================================== --- compiler-rt/lib/fuzzer/tests/CMakeLists.txt +++ compiler-rt/lib/fuzzer/tests/CMakeLists.txt @@ -103,3 +103,5 @@ OBJECT_LIBS RTfuzzer_base RTfuzzer_remote) endif() + +add_subdirectory(ipc) Index: compiler-rt/lib/fuzzer/tests/ipc/CMakeLists.txt =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/CMakeLists.txt @@ -0,0 +1,37 @@ +set(LIBFUZZER_IPC_TEST_SOURCES + IPCTestMain.cpp + IPCTestUtil.cpp + IPCTestUtilGeneric.cpp + IPCTestUtilLinux.cpp) + +set(LIBFUZZER_IPC_TEST_HEADERS + IPCTestUtil.h) + +add_compiler_rt_object_libraries(RTfuzzer_ipc_test_util + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + SOURCES ${LIBFUZZER_IPC_TEST_SOURCES} + ADDITIONAL_HEADERS ${LIBFUZZER_IPC_TEST_HEADERS} + CFLAGS ${LIBFUZZER_UNITTEST_CFLAGS} + DEPS ${LIBFUZZER_DEPS}) + +if(OS_NAME MATCHES "Linux" AND + COMPILER_RT_LIBCXX_PATH AND + COMPILER_RT_LIBCXXABI_PATH) + foreach(arch ${FUZZER_SUPPORTED_ARCH}) + target_compile_options(RTfuzzer_ipc_test_util.${arch} PRIVATE -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) + add_dependencies(RTfuzzer_ipc_test_util.${arch} libcxx_fuzzer_${arch}-build) + endforeach() +endif() + +if(COMPILER_RT_DEFAULT_TARGET_ARCH IN_LIST FUZZER_SUPPORTED_ARCH) + # libFuzzer unit tests are only run on the host machine. + set(arch ${COMPILER_RT_DEFAULT_TARGET_ARCH}) + + generate_libfuzzer_test_runtime(RTFuzzerIPCTest ${arch} + OBJECT_LIBS RTfuzzer_base RTfuzzer_ipc_base RTfuzzer_ipc_test_util) + + generate_libfuzzer_unittests(FuzzerIPC ${arch} + SOURCES IPCLinuxUnittest.cpp + RUNTIME RTFuzzerIPCTest) +endif() Index: compiler-rt/lib/fuzzer/tests/ipc/IPCLinuxUnittest.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/IPCLinuxUnittest.cpp @@ -0,0 +1,425 @@ +//===- IPCLinuxUnittest.cpp -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Unit test of IPCLinux, which tests the protocol-agnostic aspects of the IPC +// library. It checks aspects like moving bits, and has no references to +// proxies, remotes or fuzzers. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX + +#include "tests/FuzzerTestUtil.h" +#include "tests/ipc/IPCTestUtilLinux.h" +#include +#include +#include +#include +#include +#include +#include + +// Needed for linking. These are not expected to be called. +extern "C" { + +void FuzzerCrashSignalCallback(unsigned long PID) { FAIL(); } +void FuzzerMallocLimitCallback(unsigned long PID, size_t Size) { FAIL(); } + +} // extern "C" + +namespace fuzzer { +namespace ipc { +namespace { + +// Test fixtures + +class IPCLinuxTest : public TestBase { +protected: + void SetUp() override { + TestBase::SetUp(); + PID1 = Pick(); + C1.SetPid(PID1); + PID2 = Pick(); + C2.SetPid(PID2); + } + + void StartServer() { S.Start(kIpcTestAddress); } + + std::shared_ptr GetFarConnection() override { + return S.GetTestConnection(PID); + } + + bool Connect(TestConnection *TC) { + PID = TC->GetPid(); + StartServer(); + if (!TC->Connect(kIpcTestAddress)) + return false; + TC->StopReceiving(); + return true; + } + + bool IsConnected(TestConnection &C, TestConnection *S) { + uint8_t B = 0; + std::thread T([&]() { S->Receive(&B); }); + bool Result = C.Send(B); + T.join(); + return Result; + } + + bool IsNotConnected(TestConnection &C) { + uint8_t B = 0; + return !C.Send(B); + } + + template void TestSendAndReceiveValues(); + + void TearDown() override { + S.Stop(); + TestBase::TearDown(); + } + + TestServer S; + TestConnection C1, C2; + unsigned long PID1, PID2; + std::vector ErrBuf; + + std::thread Connector; +}; + +using IPCLinuxDeathTest = IPCLinuxTest; + +// Unit tests + +TEST_F(IPCLinuxTest, SimpleConnection) { + StartServer(); + EXPECT_TRUE(C1.Connect(kIpcTestAddress)); + auto S1 = S.GetTestConnection(PID1); + ASSERT_NE(S1, nullptr); + EXPECT_TRUE(IsConnected(C1, S1.get())); +} + +TEST_F(IPCLinuxDeathTest, BadConnection) { + EXPECT_DEATH( + C1.Connect("bad env var"), + "==[0-9]+== ERROR: IPC: 'bad env var' environment variable is not set."); + ASSERT_EQ(getenv(kIpcTestAddress), nullptr); + ASSERT_EQ(setenv(kIpcTestAddress, "bad address", /* overwrite =*/1), 0); + EXPECT_DEATH(C1.Connect(kIpcTestAddress), "==[0-9]+== ERROR: IPC: connect"); +} + +TEST_F(IPCLinuxTest, MultipleConnections) { + StartServer(); + + EXPECT_TRUE(C1.Connect(kIpcTestAddress)); + auto S1 = S.GetTestConnection(PID1); + ASSERT_NE(S1, nullptr); + EXPECT_TRUE(IsConnected(C1, S1.get())); + EXPECT_TRUE(IsNotConnected(C2)); + + // Re-using a PID disconnects previous holder. + EXPECT_TRUE(C1.Connect(kIpcTestAddress)); + S1 = S.GetTestConnection(PID1); + ASSERT_NE(S1, nullptr); + EXPECT_TRUE(IsConnected(C1, S1.get())); + EXPECT_TRUE(IsNotConnected(C2)); + + C2.SetPid(PID1); + EXPECT_TRUE(C2.Connect(kIpcTestAddress)); + S1 = S.GetTestConnection(PID1); + ASSERT_NE(S1, nullptr); + EXPECT_TRUE(IsNotConnected(C1)); + EXPECT_TRUE(IsConnected(C2, S1.get())); + + // Simultaneous PIDs and connections. + C1.SetPid(PID1); + C2.SetPid(PID2); + EXPECT_TRUE(C1.Connect(kIpcTestAddress)); + EXPECT_TRUE(C2.Connect(kIpcTestAddress)); + S1 = S.GetTestConnection(PID1); + auto S2 = S.GetTestConnection(PID2); + ASSERT_NE(S1, nullptr); + ASSERT_NE(S2, nullptr); + EXPECT_TRUE(IsConnected(C1, S1.get())); + EXPECT_TRUE(IsConnected(C2, S2.get())); + + // Closing a connection doesn't affect others. + { + TestConnection C3; + C3.SetPid(PID1); + EXPECT_TRUE(C3.Connect(kIpcTestAddress)); + S1 = S.GetTestConnection(PID1); + EXPECT_TRUE(IsConnected(C3, S1.get())); + } + EXPECT_TRUE(IsNotConnected(C1)); + EXPECT_TRUE(IsConnected(C2, S2.get())); +} + +TEST_F(IPCLinuxTest, StopServer) { + StartServer(); + EXPECT_TRUE(C1.Connect(kIpcTestAddress)); + EXPECT_TRUE(C2.Connect(kIpcTestAddress)); + auto S1 = S.GetTestConnection(PID1); + auto S2 = S.GetTestConnection(PID2); + ASSERT_NE(S1, nullptr); + ASSERT_NE(S2, nullptr); + EXPECT_TRUE(IsConnected(C1, S1.get())); + EXPECT_TRUE(IsConnected(C2, S2.get())); + + S.Stop(); + EXPECT_TRUE(IsNotConnected(C1)); + EXPECT_TRUE(IsNotConnected(C2)); +} + +TEST_F(IPCLinuxDeathTest, SendAndReceiveInvalidData) { + uint8_t Expected[0x10000]; + EXPECT_DEATH(C1.Send(nullptr, 1), "Assertion `Buf != nullptr' failed"); + EXPECT_DEATH(C1.Send(Expected, 0), "Assertion `BufLen != 0' failed"); +} + +TEST_F(IPCLinuxTest, SendAndReceiveData) { + ASSERT_TRUE(Connect(&C1)); + + // Generate some data; + uint8_t Expected[0x10000]; + uint8_t *Curr = Expected; + size_t Left = sizeof(Expected); + while (Left) { + auto Val = Pick(); + auto Len = std::min(Left, size_t(Pick())); + memset(Curr, Val, Len); + Curr += Len; + Left -= Len; + } + uint8_t Actual[sizeof(Expected)]; + memset(Actual, 0, sizeof(Actual)); + + // Smallest actual transmission. + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected, 1)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(Actual, 1)); + }); + EXPECT_EQ(Actual[0], Expected[0]); + EXPECT_EQ(Actual[1], 0); + + // All-but one. + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected, sizeof(Expected) - 1)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(Actual, sizeof(Actual) - 1)); + }); + + EXPECT_EQ(memcmp(Expected, Actual, sizeof(Actual) - 1), 0); + EXPECT_EQ(Actual[sizeof(Actual) - 1], 0); + + // All data. + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected, sizeof(Expected))); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(Actual, sizeof(Actual))); + }); + EXPECT_EQ(memcmp(Expected, Actual, sizeof(Actual)), 0); +} + +template void IPCLinuxTest::TestSendAndReceiveValues() { + T Expected, Actual; + + Expected = T(0); + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID1); + EXPECT_TRUE(S1->Receive(&Actual)); + }); + EXPECT_EQ(Expected, Actual); + + Expected = std::numeric_limits::min(); + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(&Actual)); + }); + EXPECT_EQ(Expected, Actual); + + Expected = std::numeric_limits::lowest(); + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(&Actual)); + }); + EXPECT_EQ(Expected, Actual); + + Expected = std::numeric_limits::max(); + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(&Actual)); + }); + EXPECT_EQ(Expected, Actual); + + Expected = Pick(); + SendFromNear([&]() { EXPECT_TRUE(C1.Send(Expected)); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->Receive(&Actual)); + }); + EXPECT_EQ(Expected, Actual); +} + +TEST_F(IPCLinuxTest, SendAndReceiveValues) { + ASSERT_TRUE(Connect(&C1)); + + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); + TestSendAndReceiveValues(); +} + +TEST_F(IPCLinuxDeathTest, SendAndReceiveInvalidMemory) { + constexpr size_t R1Len = 0x111; + int8_t R1[R1Len]; + EXPECT_DEATH(C1.SendMemory(nullptr, R1Len, false), + "Assertion `Addr != nullptr' failed."); + EXPECT_DEATH(C1.SendMemory(R1, 0, false), "Assertion `Len != 0' failed."); +} + +TEST_F(IPCLinuxTest, SendAndReceiveMemory) { + ASSERT_TRUE(Connect(&C1)); + + // Check different types and sizes. + constexpr size_t kR1Len = 0x111; + int8_t R1[kR1Len]; + memset(R1, 0x41, sizeof(R1)); + + constexpr size_t kR2Len = 0x2000; + uint16_t R2[kR2Len]; + memset(R2, 0x42, sizeof(R2)); + + constexpr size_t kR3Len = 0x3333; + int32_t R3[kR3Len]; + memset(R3, 0x43, sizeof(R3)); + + constexpr size_t kR4Len = 0x4000; + uint64_t R4[kR4Len]; + memset(R4, 0x44, sizeof(R4)); + + // Map at least two of both updatable and fixed regions. + SendFromNear([&]() { + EXPECT_TRUE(C1.SendMemory(R1, sizeof(R1), /* Updatable= */ false)); + EXPECT_TRUE(C1.SendMemory(R2, sizeof(R2), /* Updatable= */ false)); + EXPECT_TRUE(C1.SendMemory(R3, sizeof(R3), /* Updatable= */ true)); + EXPECT_TRUE(C1.SendMemory(R4, sizeof(R4), /* Updatable= */ true)); + }); + void *R1Addr, *R2Addr, *R3Addr, *R4Addr; + size_t R1Len, R2Len, R3Len, R4Len; + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_TRUE(S1->ReceiveMemory(&R1Addr, &R1Len)); + EXPECT_TRUE(S1->ReceiveMemory(&R2Addr, &R2Len)); + EXPECT_TRUE(S1->ReceiveMemory(&R3Addr, &R3Len)); + EXPECT_TRUE(S1->ReceiveMemory(&R4Addr, &R4Len)); + }); + + ASSERT_EQ(R1Len, sizeof(R1)); + EXPECT_EQ(memcmp(R1, R1Addr, sizeof(R1)), 0); + + ASSERT_EQ(R2Len, sizeof(R2)); + EXPECT_EQ(memcmp(R2, R2Addr, sizeof(R2)), 0); + + ASSERT_EQ(R3Len, sizeof(R3)); + EXPECT_EQ(memcmp(R3, R3Addr, sizeof(R3)), 0); + + ASSERT_EQ(R4Len, sizeof(R4)); + EXPECT_EQ(memcmp(R4, R4Addr, sizeof(R4)), 0); + + // Modify the original. Copies should not change. + memset(R1, 0x61, sizeof(R1)); + memset(R2, 0x62, sizeof(R2)); + memset(R3, 0x63, sizeof(R3)); + memset(R4, 0x64, sizeof(R4)); + + EXPECT_NE(memcmp(R1, R1Addr, sizeof(R1)), 0); + EXPECT_NE(memcmp(R2, R2Addr, sizeof(R2)), 0); + EXPECT_NE(memcmp(R3, R3Addr, sizeof(R3)), 0); + EXPECT_NE(memcmp(R4, R4Addr, sizeof(R4)), 0); + + // Update regions that can be updated. + C1.UpdateSharedMemory(); + + EXPECT_NE(memcmp(R1, R1Addr, sizeof(R1)), 0); + EXPECT_NE(memcmp(R2, R2Addr, sizeof(R2)), 0); + EXPECT_EQ(memcmp(R3, R3Addr, sizeof(R3)), 0); + EXPECT_EQ(memcmp(R4, R4Addr, sizeof(R4)), 0); +} + +TEST_F(IPCLinuxDeathTest, SendAndReceiveInvalidStrings) { + // Invalid arguments + char Actual[16]; + EXPECT_DEATH(C1.SendString(nullptr), "Assertion `Str != nullptr' failed."); + EXPECT_DEATH(C1.ReceiveString(nullptr, sizeof(Actual)), + "Assertion `Buf != nullptr' failed."); + EXPECT_DEATH(C1.ReceiveString(Actual, 0), "Assertion `BufLen != 0' failed."); +} + +TEST_F(IPCLinuxTest, SendAndReceiveStrings) { + ASSERT_TRUE(Connect(&C1)); + + // Empty string + std::string Expected(""); + char Actual[16]; + SendFromNear([&]() { EXPECT_TRUE(C1.SendString(Expected.c_str())); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->ReceiveString(Actual, sizeof(Actual))); + }); + EXPECT_STREQ(Expected.c_str(), Actual); + + // Normal string + Expected = "string data"; + SendFromNear([&]() { EXPECT_TRUE(C1.SendString(Expected.c_str())); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->ReceiveString(Actual, sizeof(Actual))); + }); + EXPECT_STREQ(Expected.c_str(), Actual); + + // Truncated string (due to artifically low limit of 16). + Expected = "string data that will be truncated"; + SendFromNear([&]() { EXPECT_TRUE(C1.SendString(Expected.c_str())); }); + ReceiveAtFar([&](TestConnection *S1) { + EXPECT_EQ(S1->GetPid(), PID); + EXPECT_TRUE(S1->ReceiveString(Actual, sizeof(Actual))); + }); + EXPECT_EQ(strlen(Actual), sizeof(Actual) - 1); + EXPECT_EQ(Expected.compare(0, sizeof(Actual) - 1, Actual), 0); +} + +} // namespace +} // namespace ipc +} // namespace fuzzer + +#endif // LIBFUZZER_LINUX Index: compiler-rt/lib/fuzzer/tests/ipc/IPCTestMain.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/IPCTestMain.cpp @@ -0,0 +1,24 @@ +//===- IPCTestMain.cpp -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Platform-agnostic main function for IPC unit tests. +//===----------------------------------------------------------------------===// + +#include "tests/FuzzerTestUtil.h" +#include "gtest/gtest.h" + +namespace fuzzer { +namespace ipc { + +} // namespace ipc +} // namespace fuzzer + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + testing::AddGlobalTestEnvironment(new fuzzer::TestEnvironment()); + return RUN_ALL_TESTS(); +} Index: compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtil.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtil.cpp @@ -0,0 +1,46 @@ +//===- IPCTestUtil.cpp ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Minimal utilities needed to get fuzzer IPC unit tests to link. +//===----------------------------------------------------------------------===// + +#include "FuzzerExtFunctions.h" +#include "FuzzerProxy.h" +#include "gtest/gtest.h" + +extern "C" { + +// Don't exit during unit tests. +void LLVMFuzzerIPCQuickExit(int ExitCode) {} + +// Needed for linking. FuzzerProxyDisconnect is expected to be called during +// unit testing; the rest are not. +void FuzzerProxyDisconnect(unsigned long PID) { SUCCEED(); } +void FuzzerAlarmCallback() { FAIL(); } +void FuzzerFileSizeExceedCallback() { FAIL(); } +void FuzzerGracefulExitCallback() { FAIL(); } +void FuzzerInterruptCallback() { FAIL(); } + +} // extern "C" + +namespace fuzzer { + +// These are tested by FuzzerProxy's unit tests; not here. +bool Proxy::PrintPC(const char *SymbolizedFMT, const char *FallbackFMT, + uintptr_t PC) { + return false; +} +bool Proxy::DescribePC(const char *SymbolizedFMT, uintptr_t PC, char *Desc, + size_t DescLen) { + return false; +} + +// Set up EF similar to what would be done by FuzzerDriver or FuzzerRemote. +// For testing, we wrap InitGoogleTest to keep tests looking as expected. +ExternalFunctions *EF = nullptr; + +} // namespace fuzzer Index: compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtilGeneric.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtilGeneric.cpp @@ -0,0 +1,20 @@ +//===- IPCTestUtilGeneric.cpp ---------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// No-op utilities for empty fuzzer IPC unit tests on platforms that do not have +// a provided fuzzer IPC library. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" +#if !LIBFUZZER_LINUX + +#include "gtest/gtest.h" + +// Needed for linking. This is not expected to be called. +extern "C" void FuzzerCrashSignalCallback(unsigned long PID) { FAIL(); } + +#endif // !LIBFUZZER_LINUX Index: compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtilLinux.h =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtilLinux.h @@ -0,0 +1,132 @@ +//===- IPCTestUtilLinux.h -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Utilities for fuzzer IPC unit tests on Linux. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_IPC_TEST_UTILS_LINUX_H +#define LLVM_FUZZER_IPC_TEST_UTILS_LINUX_H + +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX + +#include "FuzzerExtFunctions.h" +#include "FuzzerLock.h" +#include "ipc/IPCLinux.h" +#include "gtest/gtest.h" +#include + +namespace fuzzer { +namespace ipc { + +// Forward declaration. +class TestConnection; + +// gTest fixture base class for unit testing the Linux IPC. +class TestBase : public ::testing::Test { +public: + static constexpr const char *kIpcTestAddress = "FUZZER_IPC_TEST_ADDRESS"; + + // Invoke the responder to receive and process data sent by SendFromFar. + // Public and static to allow calling from interface functions. + static void ReceiveAtNear(std::function Responder) EXCLUDES(Mutex); + +protected: + void SetUp() override; + + virtual std::shared_ptr GetFarConnection() = 0; + + size_t BytesDiff(void *B, void *E); + + // Invoke the requestor to send data to the other end of the connection. Spin + // up a dedicated thread so the caller can process the received data. + void SendFromNear(std::function Requester); + + // Invoke the responder to process data sent using SendFromNear. Blocks until + // the data is actually sent and processed, then cleans up created thread. + void ReceiveAtFar(std::function Responder); + + // Invoke the requestor to send data from the other end of the connection. + // Blocks until the other end signals it has processed the data. + void SendFromFar(std::function Requester) + EXCLUDES(Mutex); + + void TearDown() override; + + unsigned long PID; + +private: + void ReceiveAtNearImpl(std::function Responder) EXCLUDES(Mutex); + + std::thread T; + std::mutex Mutex; + bool Stopped GUARDED_BY(Mutex); + bool Pending GUARDED_BY(Mutex); + std::condition_variable PendingCV; +}; + +// Test utility IPC server that provides methods that allows unit tests to +// create and wait for TestConnections +class TestServer : public ConnectionServer { +public: + std::shared_ptr GetTestConnection(unsigned long PID); + void WaitForConnection() EXCLUDES(Mutex); + + using ConnectionServer::Start; + using ConnectionServer::Stop; + +protected: + std::unique_ptr CreateConnection() override; + void OnConnection(Connection *C) override EXCLUDES(Mutex); + +private: + std::mutex Mutex; + bool Connected GUARDED_BY(Mutex) = false; + std::condition_variable CV; +}; + +// Test utility IPC connection that allows unit tests to directly send and +// receive data. +class TestConnection final : public Connection { +public: + TestConnection() = default; + TestConnection(TestServer *S); + ~TestConnection() override; + + using Connection::SetPid; + + bool DispatchOneLocked() override REQUIRES(GetRxMutex()); + using Connection::StopReceiving; + + bool Send(const void *Buf, size_t BufLen, int AncillaryFd = -1); + template bool Send(const T &V) { return Send(&V, sizeof(T)); } + bool SendMemory(const void *Addr, size_t Len, bool Updatable); + bool SendString(const char *Str); + + bool Receive(void *Buf, size_t BufLen, int *AncillaryFd = nullptr); + template bool Receive(T *V) { return Receive(V, sizeof(T)); } + bool ReceiveMemory(void **Addr, size_t *Len); + bool ReceiveString(char *Buf, size_t BufLen); + + using Connection::UpdateSharedMemory; + + using Connection::Close; +}; + +// This is the base struct for additional static messages recorded in unit +// tests. Several IPC unit tests send data that is eventually handled by calling +// a method of the FuzzerRemoteInterface. These tests provide implementations of +// those interface methods that populate static structs derived from this one. +struct StaticTestMessage { + unsigned long PID; +}; + +} // namespace ipc +} // namespace fuzzer + +#endif // LIBFUZZER_LINUX +#endif // LLVM_FUZZER_IPC_TEST_UTILS_LINUX_H Index: compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtilLinux.cpp =================================================================== --- /dev/null +++ compiler-rt/lib/fuzzer/tests/ipc/IPCTestUtilLinux.cpp @@ -0,0 +1,170 @@ +//===- IPCTestUtilLinux.cpp -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Utilities for fuzzer IPC unit tests on Linux. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX + +#include "ipc/IPCProxyLinux.h" +#include "tests/ipc/IPCTestUtilLinux.h" +#include + +namespace fuzzer { +namespace ipc { + +// TestBase methods + +// Allows calling into the current test from a static context. +static std::atomic Current(nullptr); + +void TestBase::SetUp() { + ::testing::Test::SetUp(); + { + std::lock_guard Lock(Mutex); + Stopped = false; + ASSERT_EQ(Current.exchange(this), nullptr); + } + srand(0); + PID = getpid(); +} + +size_t TestBase::BytesDiff(void *B, void *E) { + return reinterpret_cast(E) - reinterpret_cast(B); +} + +void TestBase::SendFromNear(std::function Requester) { + ASSERT_FALSE(T.joinable()); + T = std::thread(Requester); +} + +void TestBase::ReceiveAtFar(std::function Responder) { + ASSERT_TRUE(T.joinable()); + auto TC = GetFarConnection(); + ASSERT_NE(TC.get(), nullptr); + Responder(TC.get()); + T.join(); +} + +void TestBase::SendFromFar(std::function Requester) { + UniqueLock Lock(Mutex); + Pending = true; + auto TC = GetFarConnection(); + ASSERT_NE(TC.get(), nullptr); + Requester(TC.get()); + PendingCV.notify_one(); + while (!Stopped && Pending) + PendingCV.wait(Lock.get()); +} + +void TestBase::ReceiveAtNear(std::function Responder) { + TestBase *Test = Current; + ASSERT_NE(Test, nullptr); + Test->ReceiveAtNearImpl(Responder); +} + +void TestBase::ReceiveAtNearImpl(std::function Responder) { + UniqueLock Lock(Mutex); + while (!Stopped && !Pending) + if (HasFatalFailure()) + return; + else + PendingCV.wait(Lock.get()); + Responder(); + Pending = false; + PendingCV.notify_one(); +} + +void TestBase::TearDown() { + { + std::lock_guard Lock(Mutex); + Stopped = true; + ASSERT_EQ(Current.exchange(nullptr), this); + } + PendingCV.notify_all(); + assert(!T.joinable()); + ::testing::Test::TearDown(); +} + +// TestServer methods + +std::unique_ptr TestServer::CreateConnection() { + return std::unique_ptr(new TestConnection(this)); +} + +std::shared_ptr +TestServer::GetTestConnection(unsigned long PID) { + auto C = GetConnection(PID); + return std::static_pointer_cast(C); +} + +void TestServer::OnConnection(Connection *C) { + auto *TC = static_cast(C); + // The unit tests dispatch directly, using the methods below instead of a + // dispatch loop. + TC->StopReceiving(); + { + UniqueLock Lock(Mutex); + Connected = true; + } + CV.notify_one(); +} + +void TestServer::WaitForConnection() { + UniqueLock Lock(Mutex); + while (!Connected) + CV.wait(Lock.get()); + Connected = false; +} + +// TestConnection methods + +TestConnection::TestConnection(TestServer *S) : Connection(S) {} + +TestConnection::~TestConnection() { Reset(); } + +bool TestConnection::DispatchOneLocked() { + // See OnConnection above. + std::this_thread::yield(); + return true; +} + +bool TestConnection::Send(const void *Buf, size_t BufLen, int AncillaryFd) { + UniqueLock TxLock(GetTxMutex()); + return SendLocked(Buf, BufLen, AncillaryFd); +} + +bool TestConnection::SendMemory(const void *Addr, size_t Len, bool Updatable) { + UniqueLock TxLock(GetTxMutex()); + return SendMemoryLocked(Addr, Len, Updatable); +} + +bool TestConnection::SendString(const char *Str) { + UniqueLock TxLock(GetTxMutex()); + return SendStringLocked(Str); +} + +bool TestConnection::Receive(void *Buf, size_t BufLen, int *AncillaryFd) { + UniqueLock RxLock(GetRxMutex()); + return ReceiveLocked(Buf, BufLen, AncillaryFd); +} + +bool TestConnection::ReceiveMemory(void **Addr, size_t *Len) { + UniqueLock RxLock(GetRxMutex()); + return ReceiveMemoryLocked(Addr, Len); +} + +bool TestConnection::ReceiveString(char *Buf, size_t BufLen) { + UniqueLock RxLock(GetRxMutex()); + return ReceiveStringLocked(Buf, BufLen); +} + +} // namespace ipc +} // namespace fuzzer + +#endif // LIBFUZZER_LINUX Index: compiler-rt/test/fuzzer/CMakeLists.txt =================================================================== --- compiler-rt/test/fuzzer/CMakeLists.txt +++ compiler-rt/test/fuzzer/CMakeLists.txt @@ -21,6 +21,7 @@ list(APPEND LIBFUZZER_TEST_DEPS FuzzerUnitTests) list(APPEND LIBFUZZER_TEST_DEPS FuzzedDataProviderUnitTests) list(APPEND LIBFUZZER_TEST_DEPS FuzzerRemoteUnitTests) + list(APPEND LIBFUZZER_TEST_DEPS FuzzerIPCUnitTests) endif() add_custom_target(check-fuzzer)