diff --git a/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/elfcalls.h b/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/elfcalls.h index 8669bd680..8712f103f 120000 --- a/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/elfcalls.h +++ b/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/elfcalls.h @@ -1 +1 @@ -../../../../../../../../../src/libelfloader/native/elfcalls.h \ No newline at end of file +../../../../../../../../../src/startup/mldr/elfcalls/elfcalls.h \ No newline at end of file diff --git a/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/threads.h b/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/threads.h index dc1fcc557..e349893f0 120000 --- a/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/threads.h +++ b/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/elfcalls/threads.h @@ -1 +1 @@ -../../../../../../../../../src/libelfloader/native/threads.h \ No newline at end of file +../../../../../../../../../src/startup/mldr/elfcalls/threads.h \ No newline at end of file diff --git a/cmake/setcap.cmake b/cmake/setcap.cmake index d42b80f12..bd146ae9c 100644 --- a/cmake/setcap.cmake +++ b/cmake/setcap.cmake @@ -4,7 +4,7 @@ function(setcap file caps) install(CODE "execute_process( COMMAND ${SETCAP_EXECUTABLE} - cap_sys_rawio+ep + ${caps} ${CMAKE_INSTALL_PREFIX}/${file} RESULT_VARIABLE _SETCAP_RESULT diff --git a/cmake/wrap_elf.cmake b/cmake/wrap_elf.cmake index 8dc2cc5d4..0ec024f38 100644 --- a/cmake/wrap_elf.cmake +++ b/cmake/wrap_elf.cmake @@ -21,9 +21,9 @@ function(wrap_elf name elfname) endif (ARGC LESS 3) set(DYLIB_INSTALL_NAME "${destination}/lib${name}.dylib") - include_directories(${CMAKE_SOURCE_DIR}/src/libelfloader/native) + include_directories(${CMAKE_SOURCE_DIR}/src/startup/mldr/elfcalls) add_darling_library(${name} SHARED ${CMAKE_CURRENT_BINARY_DIR}/${name}.c) - target_link_libraries(${name} PRIVATE system elfloader) + target_link_libraries(${name} PRIVATE system) make_fat(${name}) install(TARGETS ${name} DESTINATION libexec/darling/${destination}) endfunction(wrap_elf) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f81367baa..1c45efd1d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,6 @@ include(mig) include(pyc) #add_subdirectory(external/xcbuild) -add_subdirectory(libelfloader/native) add_subdirectory(bsdln) add_definitions( @@ -44,6 +43,9 @@ include_directories(SYSTEM ${COMPILER_INC_PATH}) find_package(BISON) find_package(FLEX) +set(DARLING_SDK_RELATIVE_PATH "Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk") +set(DARLING_ROOT_RELATIVE_TO_SDK "../../../../../..") + add_subdirectory(external/cctools-port/cctools/ld64/src) add_subdirectory(external/cctools-port/cctools/ar) #add_subdirectory(external/cctools-port/cctools/as) @@ -55,6 +57,7 @@ add_subdirectory(external/cctools-port/cctools/misc) add_subdirectory(buildtools) add_subdirectory(libelfloader/wrapgen) add_subdirectory(startup) +add_subdirectory(darlingserver) include_directories(${CMAKE_SOURCE_DIR}/basic-headers) @@ -78,9 +81,6 @@ add_definitions(-target ${APPLE_TARGET_TRIPLET_PRIMARY}) include(darling_lib) include(darling_static_lib) -set(DARLING_SDK_RELATIVE_PATH "Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk") -set(DARLING_ROOT_RELATIVE_TO_SDK "../../../../../..") - file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include) include_directories( @@ -140,7 +140,6 @@ add_subdirectory(keymgr) add_subdirectory(libcache) add_subdirectory(ncurses) add_subdirectory(libiconv) -add_subdirectory(libelfloader) add_subdirectory(duct/src) add_subdirectory(libresolv) add_subdirectory(external/corecrypto) diff --git a/src/darlingserver/CMakeLists.txt b/src/darlingserver/CMakeLists.txt new file mode 100644 index 000000000..dcd357505 --- /dev/null +++ b/src/darlingserver/CMakeLists.txt @@ -0,0 +1,60 @@ +project(darlingserver) + +cmake_minimum_required(VERSION 3.10) + +include_directories( + include + internal-include + ${CMAKE_CURRENT_BINARY_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR}/internal-include +) + +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/include/darlingserver/rpc.h + ${CMAKE_CURRENT_BINARY_DIR}/internal-include/darlingserver/rpc.internal.h + ${CMAKE_CURRENT_BINARY_DIR}/src/rpc.c + COMMAND + ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate-rpc-wrappers.py + ${CMAKE_CURRENT_BINARY_DIR}/include/darlingserver/rpc.h + ${CMAKE_CURRENT_BINARY_DIR}/internal-include/darlingserver/rpc.internal.h + ${CMAKE_CURRENT_BINARY_DIR}/src/rpc.c + \"../include/darlingserver/rpc.h\" + VERBATIM + MAIN_DEPENDENCY + ${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate-rpc-wrappers.py +) + +add_custom_target(generate_dserver_rpc_wrappers + ALL + DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/include/darlingserver/rpc.h + ${CMAKE_CURRENT_BINARY_DIR}/internal-include/darlingserver/rpc.internal.h + ${CMAKE_CURRENT_BINARY_DIR}/src/rpc.c +) + +add_executable(darlingserver + src/darlingserver.cpp + src/server.cpp + src/message.cpp + src/call.cpp + src/registry.cpp + src/logging.cpp + src/process.cpp + src/thread.cpp +) + +add_dependencies(darlingserver + generate_dserver_rpc_wrappers +) + +target_compile_options(darlingserver PRIVATE -pthread -std=c++17) +target_link_options(darlingserver PRIVATE -pthread) + +install(TARGETS darlingserver DESTINATION bin) + +#file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include/darlingserver") +#create_symlink( +# "${DARLING_ROOT_RELATIVE_TO_SDK}/../../../src/darlingserver/include/darlingserver/rpc.h" +# "${CMAKE_BINARY_DIR}/${DARLING_SDK_RELATIVE_PATH}/usr/include/darlingserver/rpc.h" +#) diff --git a/src/darlingserver/internal-include/darlingserver/call.hpp b/src/darlingserver/internal-include/darlingserver/call.hpp new file mode 100644 index 000000000..cada9c488 --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/call.hpp @@ -0,0 +1,65 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_CALL_HPP_ +#define _DARLINGSERVER_CALL_HPP_ + +#include +#include + +#include +#include + +#include + +namespace DarlingServer { + class CallWithReply; + + class Call { + public: + enum class Number { + Invalid = dserver_callnum_invalid, + DSERVER_ENUM_VALUES + }; + + protected: + std::weak_ptr _thread; + MessageQueue& _replyQueue; + Address _replyAddress; + + void _stopPending(); + + public: + Call(MessageQueue& replyQueue, std::shared_ptr thread, Address replyAddress); + virtual ~Call(); + + static std::shared_ptr callFromMessage(Message&& requestMessage, MessageQueue& replyQueue); + + virtual Number number() const = 0; + std::shared_ptr thread() const; + + virtual void processCall() = 0; + + DSERVER_CLASS_DECLS; + }; + + DSERVER_CLASS_DEFS; +}; + +#endif // _DARLINGSERVER_CALL_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/logging.hpp b/src/darlingserver/internal-include/darlingserver/logging.hpp new file mode 100644 index 000000000..72ecaa91e --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/logging.hpp @@ -0,0 +1,88 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_LOGGING_HPP_ +#define _DARLINGSERVER_LOGGING_HPP_ + +#include +#include +#include + +namespace DarlingServer { + static struct EndLog {} endLog; + + class Log { + private: + std::string _category; + + enum class Type { + Debug, + Info, + Warning, + Error, + }; + + void _log(Type type, std::string message); + static std::string _typeToString(Type type); + + public: + Log(std::string category); + + Log(const Log&) = delete; + Log& operator=(const Log&) = delete; + Log(Log&&) = delete; + Log& operator=(Log&&) = delete; + + class Stream; + friend class Stream; + + Stream debug(); + Stream info(); + Stream warning(); + Stream error(); + }; + + class Log::Stream { + friend class Log; + + private: + Type _type; + Log& _log; + std::ostringstream _buffer; + + Stream(Type type, Log& log); + + public: + Stream(const Stream&) = delete; + Stream& operator=(const Stream&) = delete; + Stream(Stream&&) = delete; + Stream& operator=(Stream&&) = delete; + + template + Stream& operator<<(T value) { + _buffer << value; + return *this; + }; + + template<> + Stream& operator<<(EndLog value); + }; +}; + +#endif // _DARLINGSERVER_LOGGING_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/message.hpp b/src/darlingserver/internal-include/darlingserver/message.hpp new file mode 100644 index 000000000..b49db3862 --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/message.hpp @@ -0,0 +1,170 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_MESSAGE_HPP_ +#define _DARLINGSERVER_MESSAGE_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +namespace DarlingServer { + class Address { + private: + struct sockaddr_un _address; + size_t _size; + + public: + Address(); + Address(const struct sockaddr_un& rawAddress, size_t addressSize = sizeof(struct sockaddr_un)); + + struct sockaddr_un& raw(); + const struct sockaddr_un& raw() const; + + size_t rawSize() const; + void setRawSize(size_t newRawSize); + }; + + /** + * A C++ wrapper for local (Unix) SOCK_DGRAM/SOCK_SEQPACKET socket messages, with support for file descriptors (SCM_RIGHTS) and credentials (SCM_CREDENTIALS). + */ + class Message { + private: + struct msghdr _header; + struct iovec _dataDescriptor; + std::vector _buffer; + struct cmsghdr* _controlHeader = nullptr; + Address _socketAddress; + + void _initWithOther(Message&&); + void _cleanupSelf(); + + struct cmsghdr* _credentialsHeader(); + const struct cmsghdr* _credentialsHeader() const; + struct cmsghdr* _descriptorHeader(); + const struct cmsghdr* _descriptorHeader() const; + size_t _descriptorSpace() const; + + void _ensureCredentialsHeader(); + void _ensureDescriptorHeader(size_t descriptorSpace); + + public: + /** + * Default-initializes a Message. + * + * By default, the data buffer holds 256 bytes and the control data buffer has space for 4 descriptors. + * + * If this Message is being used to receive a message from a socket, you must preallocate + * the buffers for the recvmsg/recvmmsg call. The buffers are already preallocated to the + * sizes specified by in this constructor, however, if you wish to grow the data buffer, + * you can call pushData() with a null buffer pointer and a non-zero size. If you wish to + * grow the control data buffer, you can call pushDescriptor() with a descriptor value of `-1`. + */ + Message(size_t bufferSpace = 256, size_t descriptorSpace = 4); + ~Message(); + + Message(Message&&); + Message& operator=(Message&&); + + Message(const Message&) = delete; + Message& operator=(const Message&) = delete; + + struct msghdr& rawHeader(); + const struct msghdr& rawHeader() const; + + std::vector& data(); + const std::vector& data() const; + + Address address() const; + void setAddress(Address address); + + bool copyCredentialsOut(struct ucred& outputCredentials) const; + void copyCredentialsIn(const struct ucred& inputCredentials); + + pid_t pid() const; + void setPID(pid_t pid); + + uid_t uid() const; + void setUID(uid_t uid); + + gid_t gid() const; + void setGID(gid_t gid); + + /** + * Returns an array of all the descritors currently owned by this Message. + * + * Note that the descriptors are still owned by this Message during and after this call. + * Callers should NOT close the descriptors they receive in the returned vector. + * + * See extractDescriptor() for a method that will transfer ownership of a descriptor to the caller. + */ + std::vector descriptors() const; + + /** + * Acquires ownership of the given descriptor. + * + * Note that callers should NOT close the descriptor after a call to this method. + * This Message becomes the owner of the descriptor and will take care of closing it. + */ + void pushDescriptor(int descriptor); + + /** + * Extracts a single descriptor from this Message, transferring ownership of it to the caller. + * + * @returns The descriptor that was extracted, or `-1` if it was not found. + */ + int extractDescriptor(int descriptor); + + /** + * Like extractDescriptor(), but extracts it based on its index in the vector returned by descriptors(). + */ + int extractDescriptorAtIndex(size_t index); + + /** + * Gives up ownership of the descriptors previously held by this Message and acquires ownership + * of the descriptors passed in the given vector. + * + * Note that this method does not return the old descriptors in any way, + * so if the caller wishes to know what they were, they must call descriptors() before calling this method. + */ + void replaceDescriptors(const std::vector& newDescriptors); + }; + + /** + * A thread-safe Message queue with methods for sending and receiving messages. + */ + class MessageQueue { + private: + std::deque _messages; + std::mutex _lock; + + public: + void push(Message&& message); + std::optional pop(); + + void sendMany(int socket); + void receiveMany(int socket); + }; +}; + +#endif // _DARLINGSERVER_MESSAGE_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/process.hpp b/src/darlingserver/internal-include/darlingserver/process.hpp new file mode 100644 index 000000000..0c7e36d20 --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/process.hpp @@ -0,0 +1,76 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_PROCESS_HPP_ +#define _DARLINGSERVER_PROCESS_HPP_ + +#include +#include +#include +#include +#include + +namespace DarlingServer { + class Thread; + class Server; + + class Process { + friend class Thread; + friend class Server; + + private: + pid_t _pid; + pid_t _nspid; + int _pidfd; + mutable std::shared_mutex _rwlock; + std::vector> _threads; + std::string _vchrootPath; + + void _unregisterThreads(); + + public: + using ID = pid_t; + using NSID = ID; + + Process(ID id, NSID nsid); + ~Process(); + + Process(const Process&) = delete; + Process& operator=(const Process&) = delete; + Process(Process&&) = delete; + Process& operator=(Process&&) = delete; + + /** + * The PID of this Process as seen from darlingserver's namespace. + */ + ID id() const; + + /** + * The PID of this Process as seen from within the container (i.e. launchd's namespace). + */ + NSID nsid() const; + + std::vector> threads() const; + + std::string vchrootPath() const; + void setVchrootPath(std::string path); + }; +}; + +#endif // _DARLINGSERVER_PROCESS_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/registry.hpp b/src/darlingserver/internal-include/darlingserver/registry.hpp new file mode 100644 index 000000000..d727f19c2 --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/registry.hpp @@ -0,0 +1,171 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_REGISTRY_HPP_ +#define _DARLINGSERVER_REGISTRY_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace DarlingServer { + template + class Registry { + private: + std::unordered_map> _map; + std::unordered_map> _nsmap; + std::shared_mutex _rwlock; + + public: + using ID = typename Entry::ID; + using NSID = typename Entry::NSID; + + std::shared_ptr registerIfAbsent(NSID nsid, std::function()> entryFactory) { + std::unique_lock lock(_rwlock); + + auto it2 = _nsmap.find(nsid); + if (it2 != _nsmap.end()) { + return (*it2).second; + } + + auto entry = entryFactory(); + _map[entry->id()] = entry; + _nsmap[entry->nsid()] = entry; + return entry; + }; + + bool registerEntry(std::shared_ptr entry, bool replace = false) { + std::unique_lock lock(_rwlock); + + if (!replace && (_map.find(entry->id()) != _map.end() || _nsmap.find(entry->nsid()) != _nsmap.end())) { + return false; + } + + _map[entry->id()] = entry; + _nsmap[entry->nsid()] = entry; + return true; + }; + + bool unregisterEntryByID(ID id) { + std::unique_lock lock(_rwlock); + + auto it = _map.find(id); + + if (it == _map.end()) { + return false; + } + + std::shared_ptr entry = (*it).second; + auto it2 = _nsmap.find(entry->nsid()); + + if (it2 == _nsmap.end()) { + return false; + } + + _map.erase(it); + _nsmap.erase(it2); + return true; + }; + + bool unregisterEntryByNSID(NSID nsid) { + std::unique_lock lock(_rwlock); + + auto it2 = _nsmap.find(nsid); + + if (it2 == _nsmap.end()) { + return false; + } + + std::shared_ptr entry = (*it2).second; + auto it = _map.find(entry->id()); + + if (it == _map.end()) { + return false; + } + + _map.erase(it); + _nsmap.erase(it2); + return true; + }; + + /** + * Unregisters the given entry from this Registry. + * + * This is the recommended method for unregistering entries as it will + * actually compare pointers to ensure the entry being unregistered is + * the same as the one currently registered with the same IDs. + */ + bool unregisterEntry(std::shared_ptr entry) { + std::unique_lock lock(_rwlock); + + auto it = _map.find(entry->id()); + auto it2 = _nsmap.find(entry->nsid()); + + if (it == _map.end() || it2 == _nsmap.end()) { + return false; + } + + // note that we *want* pointer-to-pointer comparison + if ((*it).second != entry || (*it2).second != entry) { + return false; + } + + _map.erase(it); + _nsmap.erase(it2); + return true; + }; + + std::optional> lookupEntryByID(ID id) { + std::shared_lock lock(_rwlock); + + auto it = _map.find(id); + if (it == _map.end()) { + return std::nullopt; + } + + return *it; + }; + + std::optional> lookupEntryByNSID(ID nsid) { + std::shared_lock lock(_rwlock); + + auto it2 = _nsmap.find(nsid); + if (it2 == _nsmap.end()) { + return std::nullopt; + } + + return *it2; + }; + }; + + Registry& processRegistry(); + Registry& threadRegistry(); +}; + +#endif // _DARLINGSERVER_REGISTRY_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/server.hpp b/src/darlingserver/internal-include/darlingserver/server.hpp new file mode 100644 index 000000000..8ccb9306e --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/server.hpp @@ -0,0 +1,65 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_SERVER_HPP_ +#define _DARLINGSERVER_SERVER_HPP_ + +#include +#include +#include + +#include +#include +#include +#include + +namespace DarlingServer { + // NOTE: server instances MUST be created with `new` rather than as a normal local/stack variable + class Server { + private: + int _listenerSocket; + std::string _prefix; + std::string _socketPath; + int _epollFD; + MessageQueue _inbox; + MessageQueue _outbox; + WorkQueue _workQueue; + + void _worker(DarlingServer::Message message); + + public: + Server(std::string prefix); + ~Server(); + + Server(const Server&) = delete; + Server& operator=(const Server&) = delete; + Server(Server&&) = delete; + Server& operator=(Server&&) = delete; + + void start(); + + void monitorProcess(std::shared_ptr process); + + std::string prefix() const; + + static Server& sharedInstance(); + }; +}; + +#endif // _DARLINGSERVER_SERVER_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/thread.hpp b/src/darlingserver/internal-include/darlingserver/thread.hpp new file mode 100644 index 000000000..b6dbe6dfb --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/thread.hpp @@ -0,0 +1,79 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_THREAD_HPP_ +#define _DARLINGSERVER_THREAD_HPP_ + +#include +#include +#include +#include + +#include + +namespace DarlingServer { + class Process; + class Call; + + class Thread: public std::enable_shared_from_this { + friend class Process; + + private: + pid_t _tid; + pid_t _nstid; + std::weak_ptr _process; + std::shared_ptr _pendingCall; + Address _address; + mutable std::shared_mutex _rwlock; + + public: + using ID = pid_t; + using NSID = ID; + + Thread(std::shared_ptr process, NSID nsid); + ~Thread() noexcept(false); + + void registerWithProcess(); + + Thread(const Thread&) = delete; + Thread& operator=(const Thread&) = delete; + Thread(Thread&&) = delete; + Thread& operator=(Thread&&) = delete; + + std::shared_ptr process() const; + + std::shared_ptr pendingCall() const; + void setPendingCall(std::shared_ptr newPendingCall); + + /** + * The TID of this Thread as seen from darlingserver's namespace. + */ + ID id() const; + + /** + * The TID of this Thread as seen from within the container (i.e. launchd's namespace). + */ + NSID nsid() const; + + Address address() const; + void setAddress(Address address); + }; +}; + +#endif // _DARLINGSERVER_THREAD_HPP_ diff --git a/src/darlingserver/internal-include/darlingserver/workers.hpp b/src/darlingserver/internal-include/darlingserver/workers.hpp new file mode 100644 index 000000000..085b958e6 --- /dev/null +++ b/src/darlingserver/internal-include/darlingserver/workers.hpp @@ -0,0 +1,176 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#ifndef _DARLINGSERVER_WORKERS_HPP_ +#define _DARLINGSERVER_WORKERS_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DarlingServer { + template + class WorkQueue { + private: + std::mutex _queueMutex; + std::queue _workItems; + std::function _workerFunction; + std::unordered_map _workerThreads; + size_t _threadsAvailable = 0; + std::condition_variable _cv; + bool _dying = false; + + static constexpr unsigned int minimumThreads() { + return 2; + }; + + static unsigned int maximumThreads() { + unsigned int hw = std::thread::hardware_concurrency(); + if (hw > 0 && hw - 1 > minimumThreads()) { + // minus one for the main thread + return hw - 1; + } else { + return minimumThreads(); + } + }; + + static constexpr std::chrono::seconds temporaryWorkerWaitPeriod() { + return std::chrono::seconds(15); + }; + + void worker(bool permanent) { + std::optional workItem = std::nullopt; + std::unique_lock lock(_queueMutex, std::defer_lock); + + while (true) { + // acquire the lock for the condition variable + lock.lock(); + + // we're going to wait for work, so we're available + ++_threadsAvailable; + + bool waitResult; + auto predicate = [&, this]() { + // if we're dying, stop waiting + if (_dying) { + --_threadsAvailable; + return true; + } + + // if we have a work item ready, take it and stop waiting + if (_workItems.size() > 0) { + workItem = std::move(_workItems.front()); + _workItems.pop(); + --_threadsAvailable; + return true; + } + + // otherwise, let's keep waiting + return false; + }; + + if (permanent) { + // permanent workers just wait until there's work available (or the queue is being destroyed) + _cv.wait(lock, predicate); + waitResult = true; + } else { + // temporary workers wait for a certain period of time for work; if there's no work available after that period, they die + waitResult = _cv.wait_for(lock, temporaryWorkerWaitPeriod(), predicate); + } + + // if the queue is being destroyed, let the thread die + if (_dying) { + return; + } + + // if we failed to find any work items, let's die + if (!waitResult) { + auto thisThread = std::move(_workerThreads[std::this_thread::get_id()]); + _workerThreads.erase(thisThread.get_id()); + thisThread.detach(); + return; + } + + // drop the lock to perform the work + lock.unlock(); + + // perform the work + _workerFunction(std::move(workItem.value())); + workItem = std::nullopt; + } + }; + + public: + WorkQueue(std::function workerFunction): + _workerFunction(workerFunction) + { + std::scoped_lock lock(_queueMutex); + + // start the permanent worker threads + for (size_t i = 0; i < minimumThreads(); ++i) { + std::thread worker = std::thread(&WorkQueue::worker, this, true); + _workerThreads[worker.get_id()] = std::move(worker); + } + }; + ~WorkQueue() { + // tell all the threads that we're being destroyed + std::unique_lock lock(_queueMutex); + _dying = true; + lock.unlock(); + _cv.notify_all(); + + // now wait for all of the threads to die + for (auto& [id, thread]: _workerThreads) { + thread.join(); + } + }; + + WorkQueue(const WorkQueue&) = delete; + WorkQueue& operator=(const WorkQueue&) = delete; + WorkQueue(WorkQueue&&) = delete; + WorkQueue& operator=(WorkQueue&&) = delete; + + void push(WorkItem workItem) { + std::unique_lock lock(_queueMutex); + _workItems.push(std::move(workItem)); + + // if there are no threads available to perform the work AND we have less than the + // maximum number of worker threads currently active, spawn a temporary worker thread. + if (_threadsAvailable == 0 && _workerThreads.size() < maximumThreads()) { + std::thread worker = std::thread(&WorkQueue::worker, this, false); + _workerThreads[worker.get_id()] = std::move(worker); + + // note that we don't need to notify anyone in this case, since we want the new worker thread + // to take care of the new work item (it's not a problem if another thread gets to it first, though). + } else { + // otherwise, notify one of the available threads that there's work available + lock.unlock(); + _cv.notify_one(); + } + }; + }; +}; + +#endif // _DARLINGSERVER_WORKERS_HPP_ diff --git a/src/darlingserver/scripts/generate-rpc-wrappers.py b/src/darlingserver/scripts/generate-rpc-wrappers.py new file mode 100755 index 000000000..ee993d2f5 --- /dev/null +++ b/src/darlingserver/scripts/generate-rpc-wrappers.py @@ -0,0 +1,563 @@ +#!/usr/bin/env python3 + +import os +import sys +from collections import OrderedDict +import textwrap +from datetime import datetime + +# NOTE: in Python 3.7+, we can rely on dictionaries having their items in insertion order. +# unfortunately, we can't expect everyone building Darling to have Python 3.7+ installed. +calls = [ + ('checkin', [], []), + + ('checkout', [], []), + + ('vchroot_path', [ + ('buffer', 'char*'), + ('buffer_size', 'size_t'), + ], [ + ('length', 'size_t'), + ]), +] + +if len(sys.argv) < 5: + sys.exit("Usage: " + sys.argv[0] + " ") + +os.makedirs(os.path.dirname(sys.argv[1]), exist_ok=True) +os.makedirs(os.path.dirname(sys.argv[2]), exist_ok=True) +os.makedirs(os.path.dirname(sys.argv[3]), exist_ok=True) + +def to_camel_case(snake_str): + components = snake_str.split('_') + return ''.join(x.title() for x in components) + +public_header = open(sys.argv[1], "w") +internal_header = open(sys.argv[2], "w") +library_source = open(sys.argv[3], "w") +library_import = sys.argv[4] + +license_header = """\ +// This file has been auto-generated by generate-rpc-wrappers.py for use with darlingserver + +/** + * This file is part of Darling. + * + * Copyright (C) {} Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +""".format(datetime.now().year) + +public_header.write(license_header) +library_source.write(license_header) +internal_header.write(license_header) + +public_header.write("""\ +#ifndef _DARLINGSERVER_API_H_ +#define _DARLINGSERVER_API_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +""") + +public_header.write("enum dserver_callnum {\n") +public_header.write("\tdserver_callnum_invalid,\n") +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + + public_header.write("\tdserver_callnum_" + call_name + ",\n") +public_header.write("};\n") + +public_header.write("""\ + +typedef enum dserver_callnum dserver_callnum_t; + +typedef struct dserver_rpc_callhdr { + pid_t pid; + pid_t tid; + dserver_callnum_t number; +} dserver_rpc_callhdr_t; + +typedef struct dserver_rpc_replyhdr { + dserver_callnum_t number; + int code; +} dserver_rpc_replyhdr_t; + +""") + +library_source.write("""\ +#include {} + +#if !defined(dserver_rpc_hooks_msghdr_t) || !defined(dserver_rpc_hooks_iovec_t) || !defined(dserver_rpc_hooks_cmsghdr_t) || !defined(DSERVER_RPC_HOOKS_CMSG_SPACE) || !defined(DSERVER_RPC_HOOKS_CMSG_FIRSTHDR) || !defined(DSERVER_RPC_HOOKS_SOL_SOCKET) || !defined(DSERVER_RPC_HOOKS_SCM_RIGHTS) || !defined(DSERVER_RPC_HOOKS_CMSG_LEN) || !defined(DSERVER_RPC_HOOKS_CMSG_DATA) || !defined(DSERVER_RPC_HOOKS_ATTRIBUTE) + #error Missing definitions +#endif + +#ifndef dserver_rpc_hooks_get_pid +DSERVER_RPC_HOOKS_ATTRIBUTE pid_t dserver_rpc_hooks_get_pid(void); +#endif + +#ifndef dserver_rpc_hooks_get_tid +DSERVER_RPC_HOOKS_ATTRIBUTE pid_t dserver_rpc_hooks_get_tid(void); +#endif + +#ifndef dserver_rpc_hooks_get_server_address +DSERVER_RPC_HOOKS_ATTRIBUTE void* dserver_rpc_hooks_get_server_address(void); +#endif + +#ifndef dserver_rpc_hooks_get_server_address_length +DSERVER_RPC_HOOKS_ATTRIBUTE size_t dserver_rpc_hooks_get_server_address_length(void); +#endif + +#ifndef dserver_rpc_hooks_memcpy +DSERVER_RPC_HOOKS_ATTRIBUTE void* dserver_rpc_hooks_memcpy(void* destination, const void* source, size_t length); +#endif + +#ifndef dserver_rpc_hooks_send_message +DSERVER_RPC_HOOKS_ATTRIBUTE long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hooks_msghdr_t* message); +#endif + +#ifndef dserver_rpc_hooks_receive_message +DSERVER_RPC_HOOKS_ATTRIBUTE long int dserver_rpc_hooks_receive_message(int socket, dserver_rpc_hooks_msghdr_t* out_message); +#endif + +#ifndef dserver_rpc_hooks_get_bad_message_status +DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_bad_message_status(void); +#endif + +#ifndef dserver_rpc_hooks_get_communication_error_status +DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_communication_error_status(void); +#endif + +#ifndef dserver_rpc_hooks_get_broken_pipe_status +DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_broken_pipe_status(void); +#endif + +#ifndef dserver_rpc_hooks_close_fd +DSERVER_RPC_HOOKS_ATTRIBUTE void dserver_rpc_hooks_close_fd(int fd); +#endif + +#ifndef dserver_rpc_hooks_get_socket +DSERVER_RPC_HOOKS_ATTRIBUTE int dserver_rpc_hooks_get_socket(void); +#endif + +""".format(library_import)) + +internal_header.write("#define DSERVER_VALID_CALLNUM_CASES \\\n") +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + + internal_header.write("\tcase dserver_callnum_" + call_name + ": \\\n") +internal_header.write("\n") + +internal_header.write("#define DSERVER_CONSTRUCT_CASES \\\n") +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + camel_name = to_camel_case(call_name) + + internal_header.write("\tCALL_CASE(" + call_name + ", " + camel_name + "); \\\n") +internal_header.write("\n") + +internal_header.write("#define DSERVER_ENUM_VALUES \\\n") +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + camel_name = to_camel_case(call_name) + + internal_header.write("\t" + camel_name + " = dserver_callnum_" + call_name + ", \\\n") +internal_header.write("\n") + +internal_header.write("#define DSERVER_CLASS_DECLS \\\n") +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + camel_name = to_camel_case(call_name) + + internal_header.write("\tclass " + camel_name + "; \\\n") +internal_header.write("\n") + +internal_header.write("#define DSERVER_CLASS_DEFS \\\n") +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + camel_name = to_camel_case(call_name) + fd_count_in_reply = 0 + + internal_header.write(textwrap.indent(textwrap.dedent("""\ + class Call::{1}: public Call {{ \\ + friend class Call; \\ + private: \\ + {2} + public: \\ + {1}(MessageQueue& replyQueue, std::shared_ptr thread, dserver_rpc_call_{0}_t* data, Message&& requestMessage): \\ + Call(replyQueue, thread, requestMessage.address()){3} \\ + {4} + {{ \\ + """), '\t').format( + call_name, + camel_name, + ("dserver_call_" + call_name + "_t _body; \\") if len(call_parameters) > 0 else "\\", + "," if len(call_parameters) > 0 else "", + "_body(data->body) \\" if len(call_parameters) > 0 else "\\" + ) + ) + + for param in call_parameters: + param_name = param[0] + param_type = param[1] + + if param_type != "@fd": + continue + + param_type = "int" + + internal_header.write("\t\t\t_body." + param_name + " = requestMessage.extractDescriptorAtIndex(_body." + param_name + "); \\\n") + internal_header.write("\t\t}; \\\n") + + internal_header.write("\t\t~" + camel_name + "() { \\\n") + for param in call_parameters: + param_name = param[0] + param_type = param[1] + + if param_type != "@fd": + continue + + param_type = "int" + + internal_header.write("\t\t\tif (_body." + param_name + " != -1) { \\\n") + internal_header.write("\t\t\t\tclose(_body." + param_name + "); \\\n") + internal_header.write("\t\t} \\\n") + + internal_header.write(textwrap.indent(textwrap.dedent("""\ + }}; \\ + virtual Call::Number number() const {{ \\ + return Call::Number::{0}; \\ + }}; \\ + virtual void processCall(); \\ + private: \\ + """), '\t').format(camel_name)) + + internal_header.write("\t\tvoid _sendReply(int resultCode") + for param in reply_parameters: + param_name = param[0] + param_type = param[1] + + if param_type == "@fd": + param_type = "int" + fd_count_in_reply += 1 + + internal_header.write(", " + param_type + " " + param_name) + internal_header.write(") { \\\n") + + internal_header.write(textwrap.indent(textwrap.dedent("""\ + Message reply(sizeof(dserver_rpc_reply_{0}_t), {1}); \\ + reply.setAddress(_replyAddress); \\ + auto replyStruct = reinterpret_cast(reply.data().data()); \\ + replyStruct->header.number = dserver_callnum_{0}; \\ + replyStruct->header.code = resultCode; \\ + """), '\t\t\t').format(call_name, fd_count_in_reply)) + + fd_index = 0 + for param in reply_parameters: + param_name = param[0] + param_type = param[1] + val = param_name + + if param_type == "@fd": + param_type = "int" + val = str(fd_index) + internal_header.write("\t\t\treply.pushDescriptor(" + param_name + "); \\\n") + + internal_header.write("\t\t\treplyStruct->body." + param_name + " = " + val + "; \\\n") + internal_header.write("\t\t\t_replyQueue.push(std::move(reply)); \\\n") + internal_header.write("\t\t}; \\\n") + + internal_header.write("\t}; \\\n") +internal_header.write("\n") + +for call in calls: + call_name = call[0] + call_parameters = call[1] + reply_parameters = call[2] + fd_count_in_call = 0 + fd_count_in_reply = 0 + + # define the RPC call body structure + if len(call_parameters) > 0: + public_header.write("typedef struct dserver_call_" + call_name + " dserver_call_" + call_name + "_t;\n") + public_header.write("struct dserver_call_" + call_name + " {\n") + for param in call_parameters: + param_name = param[0] + param_type = param[1] + + if param_type == "@fd": + param_type = "int" + fd_count_in_call += 1 + + public_header.write("\t" + param_type + " " + param_name + ";\n") + public_header.write("};\n") + + # define the RPC call structure + public_header.write(textwrap.dedent("""\ + typedef struct dserver_rpc_call_{0} dserver_rpc_call_{0}_t; + struct dserver_rpc_call_{0} {{ + dserver_rpc_callhdr_t header; + """).format(call_name)) + if len(call_parameters) > 0: + public_header.write("\tdserver_call_" + call_name + "_t body;\n") + public_header.write("};\n") + + # define the RPC reply body structure + if len(reply_parameters) > 0: + public_header.write("typedef struct dserver_reply_" + call_name + " dserver_reply_" + call_name + "_t;\n") + public_header.write("struct dserver_reply_" + call_name + " {\n") + for param in reply_parameters: + param_name = param[0] + param_type = param[1] + + if param_type == "@fd": + param_type = "int" + fd_count_in_reply += 1 + + public_header.write("\t" + param_type + " " + param_name + ";\n") + public_header.write("};\n") + + # define the RPC reply structure + public_header.write(textwrap.dedent("""\ + typedef struct dserver_rpc_reply_{0} dserver_rpc_reply_{0}_t; + struct dserver_rpc_reply_{0} {{ + dserver_rpc_replyhdr_t header; + """).format(call_name)) + if len(reply_parameters) > 0: + public_header.write("\tdserver_reply_" + call_name + "_t body;\n") + public_header.write("};\n") + + # declare the RPC call wrapper function + # (and output the prototype part of the function definition) + tmp = "int dserver_rpc_" + call_name + "(" + public_header.write(tmp) + library_source.write(tmp) + is_first = True + for param in call_parameters: + param_name = param[0] + param_type = param[1] + + if param_type == "@fd": + param_type = "int" + + if is_first: + is_first = False + tmp = "" + else: + tmp = ", " + tmp += param_type + " " + param_name + public_header.write(tmp) + library_source.write(tmp) + + for param in reply_parameters: + param_name = param[0] + param_type = param[1] + + if param_type == "@fd": + param_type = "int" + + if is_first: + is_first = False + tmp = "" + else: + tmp = ", " + tmp += param_type + "* out_" + param_name + public_header.write(tmp) + library_source.write(tmp) + public_header.write(");\n\n") + library_source.write(") {\n") + + # define the RPC call wrapper function + library_source.write(textwrap.indent(textwrap.dedent("""\ + int status = 0; + dserver_rpc_call_{0}_t call = {{ + .header = {{ + .pid = dserver_rpc_hooks_get_pid(), + .tid = dserver_rpc_hooks_get_tid(), + .number = dserver_callnum_{0}, + }}, + """), '\t').format(call_name)) + + if len(call_parameters) > 0: + library_source.write("\t\t.body = {\n") + fd_index = 0 + for param in call_parameters: + param_name = param[0] + param_type = param[1] + val = param_name + + if param_type == "@fd": + param_type = "int" + val = "(" + param_name + " < 0) ? -1 : " + str(fd_index) + fd_index += 1 + + library_source.write("\t\t\t." + param_name + " = " + val + ",\n") + library_source.write("\t\t},\n") + + library_source.write("\t};\n") + library_source.write("\tdserver_rpc_reply_" + call_name + "_t reply;\n") + + if fd_count_in_call > 0 or fd_count_in_reply > 0: + library_source.write("\tint fds[" + max(fd_count_in_call, fd_count_in_reply) + "]") + if fd_count_in_call > 0: + library_source.write(" = { ") + is_first = True + for param in call_parameters: + param_name = param[0] + param_type = param[1] + + if param_type != "@fd": + continue + + if is_first: + is_first = False + else: + library_source.write(", ") + library_source.write(param_name) + library_source.write(" }") + library_source.write(";\n") + library_source.write("\tchar controlbuf[DSERVER_RPC_HOOKS_CMSG_SPACE(sizeof(fds))];\n") + + library_source.write(textwrap.indent(textwrap.dedent("""\ + dserver_rpc_hooks_iovec_t call_data = { + .iov_base = &call, + .iov_len = sizeof(call), + }; + dserver_rpc_hooks_msghdr_t callmsg = { + .msg_name = dserver_rpc_hooks_get_server_address(), + .msg_namelen = dserver_rpc_hooks_get_server_address_length(), + .msg_iov = &call_data, + .msg_iovlen = 1, + """), '\t')) + + if fd_count_in_call == 0: + library_source.write("\t\t.msg_control = NULL,\n") + library_source.write("\t\t.msg_controllen = 0,\n") + else: + library_source.write("\t\t.msg_control = controlbuf,\n") + library_source.write("\t\t.msg_controllen = sizeof(controlbuf),\n") + + library_source.write("\t};\n") + + if fd_count_in_call > 0: + library_source.write(textwrap.indent(textwrap.dedent("""\ + dserver_rpc_hooks_cmsghdr_t* call_cmsg = DSERVER_RPC_HOOKS_CMSG_FIRSTHDR(&callmsg); + call_cmsg->cmsg_level = DSERVER_RPC_HOOKS_SOL_SOCKET; + call_cmsg->cmsg_type = DSERVER_RPC_HOOKS_SCM_RIGHTS; + call_cmsg->cmsg_len = DSERVER_RPC_HOOKS_CMSG_LEN(sizeof(int) * {0}); + dserver_rpc_hooks_memcpy(DSERVER_RPC_HOOKS_CMSG_DATA(call_cmsg), fds, sizeof(int) * {0}); + """), '\t').format(fd_count_in_call)) + + library_source.write(textwrap.indent(textwrap.dedent("""\ + dserver_rpc_hooks_iovec_t reply_data = { + .iov_base = &reply, + .iov_len = sizeof(reply), + }; + dserver_rpc_hooks_msghdr_t replymsg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = &reply_data, + .msg_iovlen = 1, + """), '\t')) + + if fd_count_in_reply == 0: + library_source.write("\t\t.msg_control = NULL,\n") + library_source.write("\t\t.msg_controllen = 0,\n") + else: + library_source.write("\t\t.msg_control = controlbuf,\n") + library_source.write("\t\t.msg_controllen = sizeof(controlbuf),\n") + + library_source.write("\t};\n\n") + + library_source.write("\tint socket = dserver_rpc_hooks_get_socket();\n") + library_source.write("\tif (socket < 0) {") + library_source.write("\t\treturn dserver_rpc_hooks_get_broken_pipe_status();\n") + library_source.write("\t}\n\n") + + library_source.write("\tlong int long_status = dserver_rpc_hooks_send_message(socket, &callmsg);\n") + library_source.write("\tif (long_status < 0) {\n") + library_source.write("\t\treturn (int)long_status;\n") + library_source.write("\t}\n\n") + library_source.write("\tif (long_status != sizeof(call)) {\n") + library_source.write("\t\treturn dserver_rpc_hooks_get_communication_error_status();\n") + library_source.write("\t}\n\n") + + library_source.write("\tlong_status = dserver_rpc_hooks_receive_message(socket, &replymsg);\n") + library_source.write("\tif (long_status < 0) {\n") + library_source.write("\t\treturn (int)long_status;\n") + library_source.write("\t}\n\n") + library_source.write("\tif (long_status != sizeof(reply)) {\n") + library_source.write("\t\treturn dserver_rpc_hooks_get_communication_error_status();\n") + library_source.write("\t}\n\n") + + if fd_count_in_reply != 0: + library_source.write(textwrap.indent(textwrap.dedent("""\ + dserver_rpc_hooks_cmsghdr_t* reply_cmsg = DSERVER_RPC_HOOKS_CMSG_FIRSTHDR(&replymsg); + if (!reply_cmsg || reply_cmsg->cmsg_level != DSERVER_RPC_HOOKS_SOL_SOCKET || reply_cmsg->cmsg_type != DSERVER_RPC_HOOKS_SCM_RIGHTS || reply_cmsg->cmsg_len != DSERVER_RPC_HOOKS_CMSG_LEN(sizeof(int) * {0})) {{ + status = dserver_rpc_hooks_get_bad_message_status(); + return status; + }} + dserver_rpc_hooks_memcpy(fds, DSERVER_RPC_HOOKS_CMSG_DATA(reply_cmsg), sizeof(int) * {0}); + """), '\t').format(fd_count_in_reply)) + + for param in reply_parameters: + param_name = param[0] + param_type = param[1] + + if param_type == "@fd": + param_type = "int" + library_source.write("\tif (out_" + param_name + ") {\n") + library_source.write("\t\t*out_" + param_name + " = fds[reply.body." + param_name + "];\n") + library_source.write("\t} else {\n") + library_source.write("\t\tdserver_rpc_hooks_close_fd(fds[reply.body." + param_name + "]);\n") + library_source.write("\t}\n") + else: + library_source.write("\tif (out_" + param_name + ") {\n") + library_source.write("\t\t*out_" + param_name + " = reply.body." + param_name + ";\n") + library_source.write("\t}\n") + + library_source.write("\treturn status;\n") + + library_source.write("};\n\n") + +public_header.write("""\ +#ifdef __cplusplus +}; +#endif + +#endif // _DARLINGSERVER_API_H_ +""") + +public_header.close() +internal_header.close() +library_source.close() diff --git a/src/darlingserver/src/call.cpp b/src/darlingserver/src/call.cpp new file mode 100644 index 000000000..3ff2b06bb --- /dev/null +++ b/src/darlingserver/src/call.cpp @@ -0,0 +1,162 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#define _GNU_SOURCE 1 +#include +#include +#include + +std::shared_ptr DarlingServer::Call::callFromMessage(Message&& requestMessage, MessageQueue& replyQueue) { + if (requestMessage.data().size() < sizeof(dserver_rpc_callhdr_t)) { + throw std::invalid_argument("Message buffer was too small for call header"); + } + + dserver_rpc_callhdr_t* header = reinterpret_cast(requestMessage.data().data()); + std::shared_ptr result = nullptr; + std::shared_ptr process = nullptr; + std::shared_ptr thread = nullptr; + + // first, make sure we know this call number + switch (header->number) { + DSERVER_VALID_CALLNUM_CASES + break; + + default: + throw std::invalid_argument("Invalid call number"); + } + + // now let's lookup (and possibly create) the process and thread making this call + process = processRegistry().registerIfAbsent(header->pid, [&]() { + auto tmp = std::make_shared(requestMessage.pid(), header->pid); + Server::sharedInstance().monitorProcess(tmp); + return tmp; + }); + thread = threadRegistry().registerIfAbsent(header->tid, [&]() { + auto tmp = std::make_shared(process, header->tid); + tmp->setAddress(requestMessage.address()); + tmp->registerWithProcess(); + return tmp; + }); + + // finally, let's construct the call class + + #define CALL_CASE(_callName, _className) \ + case dserver_callnum_ ## _callName: { \ + if (requestMessage.data().size() < sizeof(dserver_rpc_call_ ## _callName ## _t)) { \ + throw std::invalid_argument("Message buffer was too small for dserver_call_" #_callName "_t"); \ + } \ + result = std::make_shared<_className>(replyQueue, thread, reinterpret_cast(header), std::move(requestMessage)); \ + } break; + + switch (header->number) { + DSERVER_CONSTRUCT_CASES + + default: + throw std::invalid_argument("Invalid call number"); + } + + #undef CALL_CASE + + thread->setPendingCall(result); + + return result; +}; + +DarlingServer::Call::Call(MessageQueue& replyQueue, std::shared_ptr thread, Address replyAddress): + _replyQueue(replyQueue), + _thread(thread), + _replyAddress(replyAddress) + {}; + +DarlingServer::Call::~Call() {}; + +/** + * Note that you MUST NOT have any local variables referencing `this` when this method is called. + * + * This Call object MAY be destroyed upon the return of this method. + */ +void DarlingServer::Call::_stopPending() { + // we're done processing this call; dump it + if (auto thread = _thread.lock()) { + thread->setPendingCall(nullptr); + } +}; + +void DarlingServer::Call::Checkin::processCall() { + // the Call creation already took care of registering the process and thread + _sendReply(0); + _stopPending(); +}; + +void DarlingServer::Call::Checkout::processCall() { + int code = 0; + + if (auto thread = _thread.lock()) { + if (auto process = thread->process()) { + threadRegistry().unregisterEntry(thread); + + if (thread->id() == process->id()) { + processRegistry().unregisterEntry(process); + } + } else { + code = -ESRCH; + } + } else { + code = -ESRCH; + } + + _sendReply(code); + _stopPending(); +}; + +void DarlingServer::Call::VchrootPath::processCall() { + int code = 0; + size_t fullLength = 0; + + if (auto thread = _thread.lock()) { + if (auto process = thread->process()) { + if (_body.buffer_size > 0) { + struct iovec local; + struct iovec remote; + auto tmpstr = process->vchrootPath().substr(0, _body.buffer_size - 1); + auto len = std::min(tmpstr.length() + 1, _body.buffer_size); + + fullLength = process->vchrootPath().length(); + + // note that, despite the const cast, this is safe because the local iovec data is not modified by the call + local.iov_base = const_cast(tmpstr.c_str()); + local.iov_len = len; + + remote.iov_base = _body.buffer; + remote.iov_len = len; + + if (process_vm_writev(process->id(), &local, 1, &remote, 1, 0) < 0) { + code = -errno; + } + } + } else { + code = -ESRCH; + } + } else { + code = -ESRCH; + } + + _sendReply(code, fullLength); + _stopPending(); +}; diff --git a/src/darlingserver/src/darlingserver.cpp b/src/darlingserver/src/darlingserver.cpp new file mode 100644 index 000000000..bc162c9fb --- /dev/null +++ b/src/darlingserver/src/darlingserver.cpp @@ -0,0 +1,532 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +// TODO: most of the code here was ported over from startup/darling.c; we should C++-ify it. + +#define MLDR_PATH LIBEXEC_PATH "/usr/libexec/darling/mldr" + +void fixPermissionsRecursive(const char* path, uid_t originalUID, gid_t originalGID) +{ + DIR* dir; + struct dirent* ent; + + if (chown(path, originalUID, originalGID) == -1) + fprintf(stderr, "Cannot chown %s: %s\n", path, strerror(errno)); + + dir = opendir(path); + if (!dir) + return; + + while ((ent = readdir(dir)) != NULL) + { + if (ent->d_type == DT_DIR) + { + char* subdir; + + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + + subdir = (char*) malloc(strlen(path) + 2 + strlen(ent->d_name)); + sprintf(subdir, "%s/%s", path, ent->d_name); + + fixPermissionsRecursive(subdir, originalUID, originalGID); + + free(subdir); + } + } + + closedir(dir); +} + +const char* xdgDirectory(const char* name) +{ + static char dir[4096]; + char* cmd = (char*) malloc(16 + strlen(name)); + + sprintf(cmd, "xdg-user-dir %s", name); + + FILE* proc = popen(cmd, "r"); + + free(cmd); + + if (!proc) + return NULL; + + fgets(dir, sizeof(dir)-1, proc); + + pclose(proc); + + size_t len = strlen(dir); + if (len <= 1) + return NULL; + + if (dir[len-1] == '\n') + dir[len-1] = '\0'; + return dir; +} + +void setupUserHome(const char* prefix, uid_t originalUID) +{ + char buf[4096], buf2[4096]; + + snprintf(buf, sizeof(buf), "%s/Users", prefix); + + // Remove the old /Users symlink that may exist + unlink(buf); + + // mkdir /Users + mkdir(buf, 0777); + + // mkdir /Users/Shared + strcat(buf, "/Shared"); + mkdir(buf, 0777); + + const char* home = getenv("HOME"); + + const char* login = NULL; + struct passwd* pw = getpwuid(originalUID); + + if (pw != NULL) + login = pw->pw_name; + + if (!login) + login = getlogin(); + + if (!login) + { + fprintf(stderr, "Cannot determine your user name\n"); + exit(1); + } + if (!home) + { + fprintf(stderr, "Cannot determine your home directory\n"); + exit(1); + } + + snprintf(buf, sizeof(buf), "%s/Users/%s", prefix, login); + + // mkdir /Users/$LOGIN + mkdir(buf, 0755); + + snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", home); + + strcat(buf, "/LinuxHome"); + unlink(buf); + + // symlink /Users/$LOGIN/LinuxHome -> $HOME + symlink(buf2, buf); + + static const char* xdgmap[][2] = { + { "DESKTOP", "Desktop" }, + { "DOWNLOAD", "Downloads" }, + { "PUBLICSHARE", "Public" }, + { "DOCUMENTS", "Documents" }, + { "MUSIC", "Music" }, + { "PICTURES", "Pictures" }, + { "VIDEOS", "Movies" }, + }; + + for (int i = 0; i < sizeof(xdgmap) / sizeof(xdgmap[0]); i++) + { + const char* dir = xdgDirectory(xdgmap[i][0]); + if (!dir) + continue; + + snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", dir); + snprintf(buf, sizeof(buf), "%s/Users/%s/%s", prefix, login, xdgmap[i][1]); + + unlink(buf); + symlink(buf2, buf); + } +} + +void setupCoredumpPattern(void) +{ + FILE* f = fopen("/proc/sys/kernel/core_pattern", "w"); + if (f != NULL) + { + // This is how macOS saves core dumps + fputs("/cores/core.%p\n", f); + fclose(f); + } +} + +static void wipeDir(const char* dirpath) +{ + char path[4096]; + struct dirent* ent; + DIR* dir = opendir(dirpath); + + if (!dir) + return; + + while ((ent = readdir(dir)) != NULL) + { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name); + + if (ent->d_type == DT_DIR) + { + wipeDir(path); + rmdir(path); + } + else + unlink(path); + } + + closedir(dir); +} + +void darlingPreInit(const char* prefix) +{ + // TODO: Run /usr/libexec/makewhatis + const char* dirs[] = { + "/var/tmp", + "/var/run" + }; + + char fullpath[4096]; + strcpy(fullpath, prefix); + const size_t prefixLen = strlen(fullpath); + + for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++) + { + fullpath[prefixLen] = 0; + strcat(fullpath, dirs[i]); + wipeDir(fullpath); + } +} + +void spawnLaunchd(const char* prefix) +{ + puts("Bootstrapping the container with launchd..."); + + // putenv("KQUEUE_DEBUG=1"); + + auto tmp = (std::string(prefix) + "/var/run/darlingserver.sock"); + + setenv("DYLD_ROOT_PATH", LIBEXEC_PATH, 1); + setenv("__mldr_sockpath", tmp.c_str(), 1); + execl(MLDR_PATH, "mldr!" LIBEXEC_PATH "/usr/libexec/darling/vchroot", "vchroot", prefix, "/sbin/launchd", NULL); + + fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno)); + abort(); +} + +static bool testEnvVar(const char* var_name) { + const char* var = getenv(var_name); + + if (!var) { + return false; + } + + auto len = strlen(var); + + if (len > 0 && (var[0] == '1' || var[0] == 't' || var[0] == 'T')) { + return true; + } + + return false; +}; + +static void temp_drop_privileges(uid_t uid, gid_t gid) { + // it's important to drop GID first, because non-root users can't change their GID + if (setresgid(gid, gid, 0) < 0) { + fprintf(stderr, "Failed to temporarily drop group privileges\n"); + exit(1); + } + if (setresuid(uid, uid, 0) < 0) { + fprintf(stderr, "Failed to temporarily drop user privileges\n"); + exit(1); + } +}; + +static void perma_drop_privileges(uid_t uid, gid_t gid) { + if (setresgid(gid, gid, gid) < 0) { + fprintf(stderr, "Failed to drop group privileges\n"); + exit(1); + } + if (setresuid(uid, uid, uid) < 0) { + fprintf(stderr, "Failed to drop user privileges\n"); + exit(1); + } +}; + +static void regain_privileges() { + if (seteuid(0) < 0) { + fprintf(stderr, "Failed to regain root EUID\n"); + exit(1); + } + if (setegid(0) < 0) { + fprintf(stderr, "Failed to regain root EGID\n"); + exit(1); + } + + if (setresuid(0, 0, 0) < 0) { + fprintf(stderr, "Failed to regain root privileges\n"); + exit(1); + } + if (setresgid(0, 0, 0) < 0) { + fprintf(stderr, "Failed to regain root privileges\n"); + exit(1); + } +}; + +int main(int argc, char** argv) { + const char* prefix = NULL; + uid_t originalUID = -1; + gid_t originalGID = -1; + int pipefd = -1; + bool fix_permissions = false; + pid_t launchdGlobalPID = -1; + size_t prefix_length = 0; + struct rlimit default_limit; + struct rlimit increased_limit; + FILE* nr_open_file = NULL; + int childWaitFDs[2]; + + char *opts; + char putOld[4096]; + char *p; + + if (argc < 6) { + fprintf(stderr, "darlingserver is not meant to be started manually\n"); + exit(1); + } + + prefix = argv[1]; + sscanf(argv[2], "%d", &originalUID); + sscanf(argv[3], "%d", &originalGID); + sscanf(argv[4], "%d", &pipefd); + + if (argv[5][0] == '1') { + fix_permissions = true; + } + + prefix_length = strlen(prefix); + + if (getuid() != 0 || getgid() != 0) { + fprintf(stderr, "darlingserver needs to start as root\n"); + exit(1); + } + + // temporarily drop privileges to perform some prefix work + temp_drop_privileges(originalUID, originalGID); + setupUserHome(prefix, originalUID); + //setupCoredumpPattern(); + regain_privileges(); + + // read the default rlimit so we can restore it for our children + if (getrlimit(RLIMIT_NOFILE, &default_limit) != 0) { + fprintf(stderr, "Failed to read default FD rlimit: %s\n", strerror(errno)); + exit(1); + } + + // read the system maximum + nr_open_file = fopen("/proc/sys/fs/nr_open", "r"); + if (nr_open_file == NULL) { + fprintf(stderr, "Failed to open /proc/sys/fs/nr_open: %s\n", strerror(errno)); + exit(1); + } + if (fscanf(nr_open_file, "%lu", &increased_limit.rlim_max) != 1) { + fprintf(stderr, "Failed to read /proc/sys/fs/nr_open: %s\n", strerror(errno)); + exit(1); + } + increased_limit.rlim_cur = increased_limit.rlim_max; + if (fclose(nr_open_file) != 0) { + fprintf(stderr, "Failed to close /proc/sys/fs/nr_open: %s\n", strerror(errno)); + exit(1); + } + + // now set our increased rlimit + if (setrlimit(RLIMIT_NOFILE, &increased_limit) != 0) { + fprintf(stderr, "Failed to increase FD rlimit: %s\n", strerror(errno)); + exit(1); + } + + // Since overlay cannot be mounted inside user namespaces, we have to setup a new mount namespace + // and do the mount while we can be root + if (unshare(CLONE_NEWNS) != 0) + { + fprintf(stderr, "Cannot unshare PID and mount namespaces: %s\n", strerror(errno)); + exit(1); + } + + // Because systemd marks / as MS_SHARED and we would inherit this into the overlay mount, + // causing it not to be unmounted once the init process dies. + if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL) != 0) + { + fprintf(stderr, "Cannot remount / as slave: %s\n", strerror(errno)); + exit(1); + } + + umount("/dev/shm"); + if (mount("tmpfs", "/dev/shm", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) + { + fprintf(stderr, "Cannot mount new /dev/shm: %s\n", strerror(errno)); + exit(1); + } + + opts = (char*) malloc(strlen(prefix)*2 + sizeof(LIBEXEC_PATH) + 100); + + const char* opts_fmt = "lowerdir=%s,upperdir=%s,workdir=%s.workdir,index=off"; + + sprintf(opts, opts_fmt, LIBEXEC_PATH, prefix, prefix); + + // Mount overlay onto our prefix + if (mount("overlay", prefix, "overlay", 0, opts) != 0) + { + fprintf(stderr, "Cannot mount overlay: %s\n", strerror(errno)); + exit(1); + } + + free(opts); + + // This is executed once at prefix creation + if (fix_permissions) { + const char* extra_paths[] = { + "/private/etc/passwd", + "/private/etc/master.passwd", + "/private/etc/group", + }; + char path[4096]; + + fixPermissionsRecursive(prefix, originalUID, originalGID); + + path[sizeof(path) - 1] = '\0'; + strncpy(path, prefix, sizeof(path) - 1); + for (size_t i = 0; i < sizeof(extra_paths) / sizeof(*extra_paths); ++i) { + path[prefix_length] = '\0'; + strncat(path, extra_paths[i], sizeof(path) - 1); + fixPermissionsRecursive(path, originalUID, originalGID); + } + } + + snprintf(putOld, sizeof(putOld), "%s/proc", prefix); + + // mount procfs for our new PID namespace + if (mount("proc", putOld, "proc", 0, "") != 0) + { + fprintf(stderr, "Cannot mount procfs: %s\n", strerror(errno)); + exit(1); + } + + // temporarily drop privileges and do some prefix work + temp_drop_privileges(originalUID, originalGID); + darlingPreInit(prefix); + regain_privileges(); + + // Tell the parent we're ready + write(pipefd, ".", 1); + close(pipefd); + + if (pipe(childWaitFDs) != 0) { + std::cerr << "Failed to create child waiting pipe: " << strerror(errno) << std::endl; + exit(1); + } + + // we have to use `clone` rather than `fork` to create the process in its own PID namespace + // and still be able to spawn new processes and threads of our own + struct clone_args launchdCloneArgs; + launchdCloneArgs.flags = CLONE_NEWPID; + launchdCloneArgs.pidfd = 0; + launchdCloneArgs.child_tid = 0; + launchdCloneArgs.parent_tid = 0; + launchdCloneArgs.exit_signal = SIGCHLD; + launchdCloneArgs.stack = 0; + launchdCloneArgs.stack_size = 0; + launchdCloneArgs.tls = 0; + launchdCloneArgs.set_tid = 0; + launchdCloneArgs.set_tid_size = 0; + launchdCloneArgs.cgroup = 0; + launchdGlobalPID = syscall(SYS_clone3, &launchdCloneArgs, sizeof(launchdCloneArgs)); + + // drop privileges in both parent and child + perma_drop_privileges(originalUID, originalGID); + prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + + if (launchdGlobalPID < 0) { + fprintf(stderr, "Failed to fork to start launchd: %s\n", strerror(errno)); + exit(1); + } else if (launchdGlobalPID == 0) { + // this is the child + char buf[1]; + + close(childWaitFDs[1]); + + // decrease the FD limit back to the default + if (setrlimit(RLIMIT_NOFILE, &default_limit) != 0) { + fprintf(stderr, "Failed to decrease FD limit back down for launchd: %s\n", strerror(errno)); + exit(1); + } + + // wait for the parent to give us the green light + read(childWaitFDs[0], buf, 1); + close(childWaitFDs[0]); + + spawnLaunchd(prefix); + __builtin_unreachable(); + } + + // this is the parent + close(childWaitFDs[0]); + + // create the server + auto server = new DarlingServer::Server(prefix); + + // tell the child to go ahead; the socket has been created + write(childWaitFDs[1], ".", 1); + close(childWaitFDs[1]); + + // start the main loop + server->start(); + + // this should never happen + std::cerr << "Server exited main loop!" << std::endl; + delete server; + + return 1; +}; diff --git a/src/darlingserver/src/logging.cpp b/src/darlingserver/src/logging.cpp new file mode 100644 index 000000000..f30d03ccf --- /dev/null +++ b/src/darlingserver/src/logging.cpp @@ -0,0 +1,87 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#include +#include +#include +#include +#include + +DarlingServer::Log::Log(std::string category): + _category(category) + {}; + +DarlingServer::Log::Stream::Stream(Type type, Log& log): + _type(type), + _log(log) + {}; +template<> +DarlingServer::Log::Stream& DarlingServer::Log::Stream::operator<<(EndLog value) { + _log._log(_type, _buffer.str()); + return *this; +}; + +DarlingServer::Log::Stream DarlingServer::Log::debug() { + return Stream(Type::Debug, *this); +}; + +DarlingServer::Log::Stream DarlingServer::Log::info() { + return Stream(Type::Info, *this); +}; + +DarlingServer::Log::Stream DarlingServer::Log::warning() { + return Stream(Type::Warning, *this); +}; + +DarlingServer::Log::Stream DarlingServer::Log::error() { + return Stream(Type::Error, *this); +}; + +std::string DarlingServer::Log::_typeToString(Type type) { + switch (type) { + case Type::Debug: + return "Debug"; + case Type::Info: + return "Info"; + case Type::Warning: + return "Warning"; + case Type::Error: + return "Error"; + default: + return "Unknown"; + } +}; + + +void DarlingServer::Log::_log(Type type, std::string message) { + // NOTE: we use POSIX file APIs because we want to append each message to the log file atomically, + // and as far as i can tell, C++ fstreams provide no such guarantee (that they won't write in chunks). + static int logFile = []() { + std::filesystem::path path(Server::sharedInstance().prefix() + "/private/var/log/"); + std::filesystem::create_directories(path.parent_path()); + return open(path.c_str(), O_WRONLY | O_APPEND); + }(); + + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + double secs = (double)time.tv_sec + ((double)time.tv_nsec / 1.0e9); + std::string messageToLog = "[" + std::to_string(secs) + "](" + _category + ", " + _typeToString(type) + ") " + message + "\n"; + + write(logFile, messageToLog.c_str(), messageToLog.size()); +}; diff --git a/src/darlingserver/src/message.cpp b/src/darlingserver/src/message.cpp new file mode 100644 index 000000000..e6a154540 --- /dev/null +++ b/src/darlingserver/src/message.cpp @@ -0,0 +1,539 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +DarlingServer::Address::Address() { + _address.sun_family = AF_UNIX; + memset(_address.sun_path, 0, sizeof(_address.sun_path)); + _size = sizeof(_address); +}; + +DarlingServer::Address::Address(const struct sockaddr_un& rawAddress, size_t addressLength) { + _address = rawAddress; + _size = addressLength; +}; + +struct sockaddr_un& DarlingServer::Address::raw() { + return _address; +}; + +const struct sockaddr_un& DarlingServer::Address::raw() const { + return _address; +}; + +size_t DarlingServer::Address::rawSize() const { + return _size; +}; + +void DarlingServer::Address::setRawSize(size_t newRawSize) { + _size = newRawSize; +}; + +DarlingServer::Message::Message(size_t bufferSpace, size_t descriptorSpace) { + size_t controlLen = CMSG_SPACE(sizeof(struct ucred)) + (descriptorSpace > 0 ? CMSG_SPACE(sizeof(int) * descriptorSpace) : 0); + _controlHeader = static_cast(malloc(controlLen)); + + if (!_controlHeader) { + throw std::bad_alloc(); + } + + _controlHeader->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + _controlHeader->cmsg_level = SOL_SOCKET; + _controlHeader->cmsg_type = SCM_CREDENTIALS; + struct ucred ourCreds; + ourCreds.pid = getpid(); + ourCreds.uid = getuid(); + ourCreds.gid = getgid(); + // the cmsg man page says memcpy should be used with CMSG_DATA instead of direct pointer access + memcpy(CMSG_DATA(_controlHeader), &ourCreds, sizeof(ourCreds)); + + if (descriptorSpace > 0) { + auto fdHeader = reinterpret_cast(reinterpret_cast(_controlHeader) + CMSG_SPACE(sizeof(struct ucred))); + fdHeader->cmsg_len = CMSG_LEN(sizeof(int) * descriptorSpace); + fdHeader->cmsg_level = SOL_SOCKET; + fdHeader->cmsg_type = SCM_RIGHTS; + for (size_t i = 0; i < descriptorSpace; ++i) { + const int fd = -1; + memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &fd, sizeof(int)); + } + } + + _buffer.resize(bufferSpace); + + _header.msg_name = &_socketAddress.raw(); + _header.msg_namelen = _socketAddress.rawSize(); + _header.msg_iov = &_dataDescriptor; + _header.msg_iovlen = 1; + _header.msg_control = _controlHeader; + _header.msg_controllen = controlLen; + _header.msg_flags = 0; + + _dataDescriptor.iov_base = _buffer.data(); + _dataDescriptor.iov_len = _buffer.capacity(); +}; + +void DarlingServer::Message::_initWithOther(Message&& other) { + _buffer = std::move(other._buffer); + _socketAddress = std::move(other._socketAddress); + _controlHeader = std::move(other._controlHeader); + + other._controlHeader = nullptr; + + _header = std::move(other._header); + _header.msg_name = &_socketAddress.raw(); + _header.msg_iov = &_dataDescriptor; + + _dataDescriptor.iov_base = _buffer.data(); + _dataDescriptor.iov_len = _buffer.capacity(); +}; + +void DarlingServer::Message::_cleanupSelf() { + if (_controlHeader) { + auto fdHeader = _descriptorHeader(); + if (fdHeader) { + for (size_t i = 0; i < _descriptorSpace(); ++i) { + int fd = -1; + memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int)); + if (fd != -1) { + close(fd); + } + } + } + free(_controlHeader); + _controlHeader = nullptr; + } +}; + +struct cmsghdr* DarlingServer::Message::_credentialsHeader() { + return const_cast(const_cast(this)->_credentialsHeader()); +}; + +const struct cmsghdr* DarlingServer::Message::_credentialsHeader() const { + const struct cmsghdr* hdr = CMSG_FIRSTHDR(&_header); + + while (hdr) { + if (hdr->cmsg_level == SOL_SOCKET && hdr->cmsg_type == SCM_CREDENTIALS) { + return hdr; + } + + hdr = CMSG_NXTHDR(const_cast(&_header), const_cast(hdr)); + } + + return nullptr; +}; + +struct cmsghdr* DarlingServer::Message::_descriptorHeader() { + return const_cast(const_cast(this)->_descriptorHeader()); +}; + +const struct cmsghdr* DarlingServer::Message::_descriptorHeader() const { + const struct cmsghdr* hdr = CMSG_FIRSTHDR(&_header); + + while (hdr) { + if (hdr->cmsg_level == SOL_SOCKET && hdr->cmsg_type == SCM_RIGHTS) { + return hdr; + } + + hdr = CMSG_NXTHDR(const_cast(&_header), const_cast(hdr)); + } + + return nullptr; +}; + +size_t DarlingServer::Message::_descriptorSpace() const { + auto hdr = _descriptorHeader(); + + if (!hdr) { + return 0; + } + + return (hdr->cmsg_len - (reinterpret_cast(CMSG_DATA(hdr)) - reinterpret_cast(hdr))) / sizeof(int); +}; + +DarlingServer::Message::~Message() { + _cleanupSelf(); +}; + +DarlingServer::Message::Message(Message&& other) { + _initWithOther(std::move(other)); +}; + +DarlingServer::Message& DarlingServer::Message::operator=(Message&& other) { + _cleanupSelf(); + _initWithOther(std::move(other)); + return *this; +}; + +struct msghdr& DarlingServer::Message::rawHeader() { + return _header; +}; + +const struct msghdr& DarlingServer::Message::rawHeader() const { + return _header; +}; + +std::vector& DarlingServer::Message::data() { + return _buffer; +}; + +const std::vector& DarlingServer::Message::data() const { + return _buffer; +}; + +DarlingServer::Address DarlingServer::Message::address() const { + return _socketAddress; +}; + +void DarlingServer::Message::setAddress(Address address) { + _socketAddress = address; + _header.msg_namelen = _socketAddress.rawSize(); +}; + +std::vector DarlingServer::Message::descriptors() const { + std::vector descriptors; + auto fdHeader = _descriptorHeader(); + + if (fdHeader) { + for (size_t i = 0; i < _descriptorSpace(); ++i) { + int fd = -1; + memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int)); + if (fd != -1) { + descriptors.push_back(fd); + } + } + } + + return descriptors; +}; + +void DarlingServer::Message::pushDescriptor(int descriptor) { + if (auto fdHeader = _descriptorHeader()) { + for (size_t i = 0; i < _descriptorSpace(); ++i) { + int fd = -1; + memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int)); + if (fd != -1) { + continue; + } + + memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &descriptor, sizeof(int)); + return; + } + } + + auto oldSpace = _descriptorSpace(); + _ensureDescriptorHeader(oldSpace + 1); + memcpy(CMSG_DATA(_descriptorHeader()) + (sizeof(int) * oldSpace), &descriptor, sizeof(int)); +}; + +int DarlingServer::Message::extractDescriptor(int descriptor) { + if (auto fdHeader = _descriptorHeader()) { + for (size_t i = 0; i < _descriptorSpace(); ++i) { + int fd = -1; + memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int)); + if (fd != descriptor) { + continue; + } + + fd = -1; + memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &fd, sizeof(int)); + return descriptor; + } + } + + return -1; +}; + +int DarlingServer::Message::extractDescriptorAtIndex(size_t index) { + if (auto fdHeader = _descriptorHeader()) { + for (size_t i = 0, presentIndex = 0; i < _descriptorSpace(); ++i) { + int fd = -1; + memcpy(&fd, CMSG_DATA(fdHeader) + (sizeof(int) * i), sizeof(int)); + if (fd == -1) { + continue; + } + + if (presentIndex++ != index) { + continue; + } + + int tmp = -1; + memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &tmp, sizeof(int)); + return fd; + } + } + + return -1; +}; + +void DarlingServer::Message::replaceDescriptors(const std::vector& newDescriptors) { + _ensureDescriptorHeader(newDescriptors.size()); + memcpy(CMSG_DATA(_descriptorHeader()), newDescriptors.data(), sizeof(int) * newDescriptors.size()); +}; + +bool DarlingServer::Message::copyCredentialsOut(struct ucred& outputCredentials) const { + auto credHeader = _credentialsHeader(); + + if (credHeader) { + memcpy(&outputCredentials, CMSG_DATA(credHeader), sizeof(outputCredentials)); + return true; + } else { + return false; + } +}; + +void DarlingServer::Message::copyCredentialsIn(const struct ucred& inputCredentials) { + _ensureCredentialsHeader(); + memcpy(CMSG_DATA(_credentialsHeader()), &inputCredentials, sizeof(inputCredentials)); +}; + +void DarlingServer::Message::_ensureCredentialsHeader() { + if (_credentialsHeader()) { + return; + } + + auto fdHeader = _descriptorHeader(); + auto descSpace = _descriptorSpace(); + size_t controlLen = CMSG_SPACE(sizeof(struct ucred)) + (descSpace > 0 ? CMSG_SPACE(sizeof(int) * descSpace) : 0); + auto tmp = static_cast(malloc(controlLen)); + auto old = _controlHeader; + struct cmsghdr* credHeader = nullptr; + + if (!tmp) { + throw std::bad_alloc(); + } + + _controlHeader = tmp; + _header.msg_control = _controlHeader; + + credHeader = _controlHeader; + + if (descSpace > 0) { + auto newFdHeader = reinterpret_cast(reinterpret_cast(_controlHeader) + CMSG_SPACE(sizeof(struct ucred))); + memcpy(newFdHeader, fdHeader, CMSG_SPACE(sizeof(int) * descSpace)); + } + + free(old); + + _header.msg_controllen = controlLen; + + credHeader->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + credHeader->cmsg_level = SOL_SOCKET; + credHeader->cmsg_type = SCM_CREDENTIALS; + + struct ucred creds; + creds.pid = getpid(); + creds.uid = getuid(); + creds.gid = getgid(); + memcpy(CMSG_DATA(credHeader), &creds, sizeof(struct ucred)); +}; + +void DarlingServer::Message::_ensureDescriptorHeader(size_t newSpace) { + if (_descriptorSpace() == newSpace) { + return; + } + + auto credHeader = _credentialsHeader(); + size_t oldSpace = _descriptorSpace(); + size_t controlLen = (credHeader ? CMSG_SPACE(sizeof(struct ucred)) : 0) + (newSpace == 0 ? 0 : CMSG_SPACE(sizeof(int) * newSpace)); + + if (controlLen == 0) { + free(_controlHeader); + _controlHeader = nullptr; + _header.msg_control = nullptr; + _header.msg_controllen = 0; + return; + } + + auto tmp = static_cast(malloc(controlLen)); + auto old = _controlHeader; + struct cmsghdr* fdHeader = nullptr; + + if (!tmp) { + throw std::bad_alloc(); + } + + _controlHeader = tmp; + _header.msg_control = _controlHeader; + + if (credHeader) { + memcpy(_controlHeader, credHeader, CMSG_SPACE(sizeof(struct ucred))); + fdHeader = reinterpret_cast(reinterpret_cast(_controlHeader) + CMSG_SPACE(sizeof(struct ucred))); + } else { + fdHeader = _controlHeader; + } + + free(old); + + _header.msg_controllen = controlLen; + + fdHeader->cmsg_len = CMSG_LEN(sizeof(int) * newSpace); + fdHeader->cmsg_level = SOL_SOCKET; + fdHeader->cmsg_type = SCM_RIGHTS; + + for (size_t i = oldSpace; i < newSpace; ++i) { + int fd = -1; + memcpy(CMSG_DATA(fdHeader) + (sizeof(int) * i), &fd, sizeof(int)); + } +}; + +pid_t DarlingServer::Message::pid() const { + struct ucred creds; + + if (copyCredentialsOut(creds)) { + return creds.pid; + } else { + return -1; + } +}; + +void DarlingServer::Message::setPID(pid_t pid) { + _ensureCredentialsHeader(); + struct ucred creds; + copyCredentialsOut(creds); + creds.pid = pid; + copyCredentialsIn(creds); +}; + +uid_t DarlingServer::Message::uid() const { + struct ucred creds; + + if (copyCredentialsOut(creds)) { + return creds.uid; + } else { + return -1; + } +}; + +void DarlingServer::Message::setUID(uid_t uid) { + _ensureCredentialsHeader(); + struct ucred creds; + copyCredentialsOut(creds); + creds.uid = uid; + copyCredentialsIn(creds); +}; + +gid_t DarlingServer::Message::gid() const { + struct ucred creds; + + if (copyCredentialsOut(creds)) { + return creds.gid; + } else { + return -1; + } +}; + +void DarlingServer::Message::setGID(gid_t gid) { + _ensureCredentialsHeader(); + struct ucred creds; + copyCredentialsOut(creds); + creds.gid = gid; + copyCredentialsIn(creds); +}; + +void DarlingServer::MessageQueue::push(Message&& message) { + std::scoped_lock lock(_lock); + _messages.push_back(std::move(message)); +}; + +std::optional DarlingServer::MessageQueue::pop() { + std::scoped_lock lock(_lock); + if (_messages.size() > 0) { + Message message = std::move(_messages.front()); + _messages.pop_front(); + return message; + } else { + return std::nullopt; + } +}; + +void DarlingServer::MessageQueue::sendMany(int socket) { + std::scoped_lock lock(_lock); + struct mmsghdr mmsgs[16]; + size_t len = 0; + int ret = 0; + + while (ret >= 0) { + len = 0; + for (size_t i = 0; i < _messages.size() && i < sizeof(mmsgs) / sizeof(*mmsgs); ++i) { + mmsgs[i].msg_hdr = _messages[i].rawHeader(); + mmsgs[i].msg_len = 0; + ++len; + } + + if (len == 0) { + break; + } + + ret = sendmmsg(socket, mmsgs, len, MSG_DONTWAIT); + + if (ret < 0) { + if (errno == EAGAIN) { + break; + } else if (errno == EINTR) { + ret = 0; + } else { + throw std::system_error(errno, std::generic_category(), "Failed to send messages through socket"); + } + } + + for (size_t i = 0; i < ret; ++i) { + _messages.pop_front(); + } + } +}; + +void DarlingServer::MessageQueue::receiveMany(int socket) { + std::scoped_lock lock(_lock); + struct mmsghdr mmsgs[16]; + int ret = 0; + + while (ret >= 0) { + std::array messages; + + for (size_t i = 0; i < messages.size(); ++i) { + mmsgs[i].msg_hdr = messages[i].rawHeader(); + mmsgs[i].msg_len = 0; + } + + ret = recvmmsg(socket, mmsgs, sizeof(mmsgs) / sizeof(*mmsgs), MSG_CMSG_CLOEXEC | MSG_DONTWAIT, nullptr); + + if (ret < 0) { + if (errno == EAGAIN) { + break; + } else if (errno == EINTR) { + ret = 0; + } else { + throw std::system_error(errno, std::generic_category(), "Failed to receive messages through socket"); + } + } + + for (size_t i = 0; i < ret; ++i) { + messages[i].rawHeader() = mmsgs[i].msg_hdr; + messages[i].data().resize(mmsgs[i].msg_len); + messages[i].setAddress(Address(*(const struct sockaddr_un*)mmsgs[i].msg_hdr.msg_name, mmsgs[i].msg_hdr.msg_namelen)); + _messages.push_back(std::move(messages[i])); + } + } +}; diff --git a/src/darlingserver/src/process.cpp b/src/darlingserver/src/process.cpp new file mode 100644 index 000000000..95ca21b76 --- /dev/null +++ b/src/darlingserver/src/process.cpp @@ -0,0 +1,83 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#include +#include +#include +#include + +DarlingServer::Process::Process(ID id, NSID nsid): + _pid(id), + _nspid(nsid) +{ + _pidfd = syscall(SYS_pidfd_open, _pid, 0); + if (_pidfd < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to open pidfd for process"); + } +}; + +DarlingServer::Process::~Process() { + close(_pidfd); + + _unregisterThreads(); +}; + +void DarlingServer::Process::_unregisterThreads() { + std::unique_lock lock(_rwlock); + while (!_threads.empty()) { + auto thread = _threads.front().lock(); + lock.unlock(); + if (thread) { + thread->_process = std::weak_ptr(); + threadRegistry().unregisterEntry(thread); + } + lock.lock(); + } +}; + +DarlingServer::Process::ID DarlingServer::Process::id() const { + return _pid; +}; + +DarlingServer::Process::NSID DarlingServer::Process::nsid() const { + return _nspid; +}; + +std::vector> DarlingServer::Process::threads() const { + std::vector> result; + std::shared_lock lock(_rwlock); + + for (auto& maybeThread: _threads) { + if (auto thread = maybeThread.lock()) { + result.push_back(thread); + } + } + + return result; +}; + +std::string DarlingServer::Process::vchrootPath() const { + std::shared_lock lock(_rwlock); + return _vchrootPath; +}; + +void DarlingServer::Process::setVchrootPath(std::string path) { + std::unique_lock lock(_rwlock); + _vchrootPath = path; +}; diff --git a/src/darlingserver/src/registry.cpp b/src/darlingserver/src/registry.cpp new file mode 100644 index 000000000..1fecf8795 --- /dev/null +++ b/src/darlingserver/src/registry.cpp @@ -0,0 +1,30 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#include + +DarlingServer::Registry& DarlingServer::processRegistry() { + static Registry registry; + return registry; +}; + +DarlingServer::Registry& DarlingServer::threadRegistry() { + static Registry registry; + return registry; +}; diff --git a/src/darlingserver/src/server.cpp b/src/darlingserver/src/server.cpp new file mode 100644 index 000000000..139e363ef --- /dev/null +++ b/src/darlingserver/src/server.cpp @@ -0,0 +1,152 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static DarlingServer::Server* sharedInstancePointer = nullptr; + +DarlingServer::Server::Server(std::string prefix): + _prefix(prefix), + _socketPath(_prefix + "/var/run/darlingserver.sock"), + _workQueue(std::bind(&Server::_worker, this, std::placeholders::_1)) +{ + sharedInstancePointer = this; + + // remove the old socket (if it exists) + unlink(_socketPath.c_str()); + + // create the socket + _listenerSocket = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); + if (_listenerSocket < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to create socket"); + } + + int passCred = 1; + if (setsockopt(_listenerSocket, SOL_SOCKET, SO_PASSCRED, &passCred, sizeof(passCred)) < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to set SO_PASSCRED on socket"); + } + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + strncpy(addr.sun_path, _socketPath.c_str(), sizeof(addr.sun_path) - 1); + + if (bind(_listenerSocket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + throw std::system_error(errno, std::generic_category(), "Failed to bind socket"); + } + + _epollFD = epoll_create1(EPOLL_CLOEXEC); + if (_epollFD < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to create epoll context"); + } + + struct epoll_event settings; + settings.data.ptr = this; + settings.events = EPOLLIN | EPOLLOUT; + + if (epoll_ctl(_epollFD, EPOLL_CTL_ADD, _listenerSocket, &settings) < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to add listener socket to epoll context"); + } +}; + +DarlingServer::Server::~Server() { + close(_epollFD); + close(_listenerSocket); + unlink(_socketPath.c_str()); +}; + +void DarlingServer::Server::start() { + while (true) { + struct epoll_event events[16]; + int ret = epoll_wait(_epollFD, events, 16, -1); + + if (ret < 0) { + if (errno == EINTR) { + continue; + } + + throw std::system_error(errno, std::generic_category(), "Failed to wait on epoll context"); + } + + for (size_t i = 0; i < ret; ++i) { + struct epoll_event* event = &events[i]; + + if (event->data.ptr == this) { + if (event->events & EPOLLIN) { + _inbox.receiveMany(_listenerSocket); + + // TODO: receive messages directly onto the work queue + while (auto msg = _inbox.pop()) { + _workQueue.push(std::move(msg.value())); + } + } + + if (event->events & EPOLLOUT) { + _outbox.sendMany(_listenerSocket); + } + } else if (event->events & EPOLLIN) { + std::shared_ptr& process = *reinterpret_cast*>(event->data.ptr); + + if (epoll_ctl(_epollFD, EPOLL_CTL_DEL, process->_pidfd, NULL) < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to remove process handle from epoll context"); + } + + process->_unregisterThreads(); + processRegistry().unregisterEntry(process); + + delete &process; + } + } + } +}; + +void DarlingServer::Server::monitorProcess(std::shared_ptr process) { + struct epoll_event settings; + settings.data.ptr = new std::shared_ptr(process); + settings.events = EPOLLIN; + + if (epoll_ctl(_epollFD, EPOLL_CTL_ADD, process->_pidfd, &settings) < 0) { + throw std::system_error(errno, std::generic_category(), "Failed to add process descriptor to epoll context"); + } +}; + +DarlingServer::Server& DarlingServer::Server::sharedInstance() { + return *sharedInstancePointer; +}; + +std::string DarlingServer::Server::prefix() const { + return _prefix; +}; + +void DarlingServer::Server::_worker(DarlingServer::Message message) { + auto call = DarlingServer::Call::callFromMessage(std::move(message), _outbox); + call->processCall(); +}; diff --git a/src/darlingserver/src/thread.cpp b/src/darlingserver/src/thread.cpp new file mode 100644 index 000000000..2d83cdcf2 --- /dev/null +++ b/src/darlingserver/src/thread.cpp @@ -0,0 +1,122 @@ +/** + * This file is part of Darling. + * + * Copyright (C) 2021 Darling developers + * + * Darling is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Darling is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Darling. If not, see . + */ + +#include +#include +#include +#include + +DarlingServer::Thread::Thread(std::shared_ptr process, NSID nsid): + _nstid(nsid), + _process(process) +{ + _tid = -1; + + for (const auto& entry: std::filesystem::directory_iterator("/proc/" + std::to_string(process->id()) + "/task")) { + std::ifstream statusFile(entry.path() / "status"); + std::string line; + + while (std::getline(statusFile, line)) { + if (line.substr(0, sizeof("NSpid") - 1) == "NSpid") { + auto pos = line.find_last_of('\t'); + std::string id; + + if (pos != line.npos) { + id = line.substr(pos + 1); + } + + if (id.empty()) { + throw std::runtime_error("Failed to parse thread ID"); + } + + if (std::stoi(id) != _nstid) { + continue; + } + + _tid = std::stoi(entry.path().filename().string()); + + break; + } + } + } + + if (_tid == -1) { + throw std::runtime_error("Failed to find thread ID within darlingserver's namespace"); + } +}; + +void DarlingServer::Thread::registerWithProcess() { + auto process = _process.lock(); + std::unique_lock lock(process->_rwlock); + process->_threads.push_back(shared_from_this()); +}; + +DarlingServer::Thread::~Thread() noexcept(false) { + auto process = _process.lock(); + if (!process) { + // the process is unregistering us + return; + } + + std::unique_lock lock(process->_rwlock); + auto it = process->_threads.begin(); + while (it != process->_threads.end()) { + if (auto thread = it->lock()) { + if (thread.get() == this) { + break; + } + } + } + if (it == process->_threads.end()) { + throw std::runtime_error("Thread was not registered with Process"); + } + process->_threads.erase(it); +}; + +DarlingServer::Thread::ID DarlingServer::Thread::id() const { + return _tid; +}; + +DarlingServer::Thread::NSID DarlingServer::Thread::nsid() const { + return _nstid; +}; + +std::shared_ptr DarlingServer::Thread::process() const { + return _process.lock(); +}; + +std::shared_ptr DarlingServer::Thread::pendingCall() const { + std::shared_lock lock(_rwlock); + return _pendingCall; +}; + +void DarlingServer::Thread::setPendingCall(std::shared_ptr newPendingCall) { + std::unique_lock lock(_rwlock); + _pendingCall = newPendingCall; +}; + +DarlingServer::Address DarlingServer::Thread::address() const { + std::shared_lock lock(_rwlock); + return _address; +}; + +void DarlingServer::Thread::setAddress(Address address) { + std::unique_lock lock(_rwlock); + _address = address; +}; diff --git a/src/external/cocotron b/src/external/cocotron index d7cc4e3ff..7edb4834c 160000 --- a/src/external/cocotron +++ b/src/external/cocotron @@ -1 +1 @@ -Subproject commit d7cc4e3ff153f8986b911fbb0a7f7b9d6d74022c +Subproject commit 7edb4834c7af363e92183f7720c8991d2c518e2a diff --git a/src/external/darling-dmg b/src/external/darling-dmg index 34d3a3460..8a2831704 160000 --- a/src/external/darling-dmg +++ b/src/external/darling-dmg @@ -1 +1 @@ -Subproject commit 34d3a346039f25a407c64aca1c7ecbdd9af85933 +Subproject commit 8a28317048006b2379b724bc32adad9dd0523f02 diff --git a/src/kernel/emulation/linux/bsdthread/workq_kernreturn.c b/src/kernel/emulation/linux/bsdthread/workq_kernreturn.c index 9cd0e5be6..c209f5445 100644 --- a/src/kernel/emulation/linux/bsdthread/workq_kernreturn.c +++ b/src/kernel/emulation/linux/bsdthread/workq_kernreturn.c @@ -19,7 +19,7 @@ #include // much easier to include than libpthread's `internal.h` -#include "../../../../libelfloader/native/dthreads.h" +#include "../../../../startup/mldr/elfcalls/dthreads.h" #define WQ_MAX_THREADS 64 diff --git a/src/kernel/emulation/linux/elfcalls_wrapper.c b/src/kernel/emulation/linux/elfcalls_wrapper.c index 213667ca7..39f8d76d3 100644 --- a/src/kernel/emulation/linux/elfcalls_wrapper.c +++ b/src/kernel/emulation/linux/elfcalls_wrapper.c @@ -1,21 +1,24 @@ #include "elfcalls_wrapper.h" #include #include +#include "simple.h" -static struct elf_calls* _elfcalls; +extern struct elf_calls* _elfcalls; struct elf_calls* elfcalls(void) { if (!_elfcalls) { - void* module = dlopen("/usr/lib/darling/libelfloader.dylib", RTLD_NOW); + //void* module = dlopen("/usr/lib/darling/libelfloader.dylib", RTLD_NOW); // if (!module) // __simple_printf("Load error: %s\n", dlerror()); // struct elf_calls** ptr = (struct elf_calls**) dlsym(module, "_elfcalls"); // __simple_printf("_elfcalls is at %p\n", ptr); // __simple_printf("*_elfcalls = %p\n", *ptr); - _elfcalls = *(struct elf_calls**) dlsym(module, "_elfcalls"); + //_elfcalls = *(struct elf_calls**) dlsym(module, "_elfcalls"); + __simple_printf("elfcalls not found?\n"); + __simple_abort(); } return _elfcalls; } diff --git a/src/kernel/emulation/linux/mach/lkm.c b/src/kernel/emulation/linux/mach/lkm.c index ab93b03a5..bbc368dc3 100644 --- a/src/kernel/emulation/linux/mach/lkm.c +++ b/src/kernel/emulation/linux/mach/lkm.c @@ -9,6 +9,7 @@ #include "../../../libsyscall/wrappers/_libkernel_init.h" #include "../simple.h" #include "../misc/ioctl.h" +#include extern int sys_open(const char*, int, int); extern int close_internal(int); @@ -22,8 +23,15 @@ extern _libkernel_functions_t _libkernel_functions; static int driver_fd = -1; +VISIBLE +struct elf_calls* _elfcalls; + void mach_driver_init(const char** applep) { + // DARLINGSERVER/MLDR TESTING + __simple_printf("We're being initialized...\n"); + __builtin_unreachable(); + #ifdef VARIANT_DYLD if (applep != NULL) { @@ -33,7 +41,11 @@ void mach_driver_init(const char** applep) if (strncmp(applep[i], "kernfd=", 7) == 0) { driver_fd = __simple_atoi(applep[i] + 7, NULL); - break; + } + if (strncmp(applep[i], "elf_calls=", 10) == 0) + { + uintptr_t table = (uintptr_t) __simple_atoi16(applep[i] + 10, NULL); + _elfcalls = (struct elf_calls*) table; } } } diff --git a/src/kernel/emulation/linux/simple.c b/src/kernel/emulation/linux/simple.c index 1fe64ac0b..3b4ca42d7 100644 --- a/src/kernel/emulation/linux/simple.c +++ b/src/kernel/emulation/linux/simple.c @@ -5,6 +5,8 @@ #include #include #include "mach/lkm.h" +#include "signal/kill.h" +#include extern char* memchr(char* buf, int c, __SIZE_TYPE__ n); @@ -569,3 +571,8 @@ have_nl: return out; } +__attribute__ ((visibility ("default"))) +void __simple_abort(void) { + sys_kill(0, SIGABRT, 1); + __builtin_unreachable(); +}; diff --git a/src/kernel/emulation/linux/simple.h b/src/kernel/emulation/linux/simple.h index 30450d72e..6185eff2e 100644 --- a/src/kernel/emulation/linux/simple.h +++ b/src/kernel/emulation/linux/simple.h @@ -34,6 +34,9 @@ struct simple_readline_buf void __simple_readline_init(struct simple_readline_buf* buf); char* __simple_readline(int fd, struct simple_readline_buf* buf, char* out, int max_out); +__attribute__((noreturn)) +void __simple_abort(void); + #ifdef __cplusplus }; #endif diff --git a/src/kernel/libsyscall/wrappers/_libkernel_init.c b/src/kernel/libsyscall/wrappers/_libkernel_init.c index af65a13ee..c873b9d9e 100644 --- a/src/kernel/libsyscall/wrappers/_libkernel_init.c +++ b/src/kernel/libsyscall/wrappers/_libkernel_init.c @@ -33,9 +33,8 @@ #include "_libkernel_init.h" #ifdef DARLING -#include - extern int mach_init(const char** applep); +extern void sigexc_setup(void); #else extern int mach_init(void); #endif @@ -49,44 +48,21 @@ bool _os_xbs_chrooted; /* dlsym() funcptr is for legacy support in exc_catcher */ void* (*_dlsym)(void*, const char*) __attribute__((visibility("hidden"))); -#ifdef DARLING -extern int strncmp(const char *s1, const char *s2, __SIZE_TYPE__ n); -extern unsigned long long __simple_atoi16(const char* str, const char** endp); -#endif - __attribute__((visibility("hidden"))) _libkernel_functions_t _libkernel_functions; -#ifdef DARLING -__attribute__((visibility("hidden"))) -struct elf_calls* _elfcalls; -#endif - void __libkernel_init(_libkernel_functions_t fns, const char *envp[] __attribute__((unused)), const char *apple[], const struct ProgramVars *vars __attribute__((unused))) { -#ifdef DARLING - int i; -#endif - _libkernel_functions = fns; if (fns->dlsym) { _dlsym = fns->dlsym; } #ifdef DARLING - for (i = 0; apple[i] != NULL; i++) - { - if (strncmp(apple[i], "elf_calls=", 10) == 0) - { - uintptr_t table = (uintptr_t) __simple_atoi16(apple[i] + 10, NULL); - _elfcalls = (struct elf_calls*) table; - } - } - mach_init(apple); sigexc_setup(); #else diff --git a/src/libelfloader/CMakeLists.txt b/src/libelfloader/CMakeLists.txt deleted file mode 100644 index 1f30d7e30..000000000 --- a/src/libelfloader/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -project(libelfloader) - -set(elfloader_sources - loader.c -) - -set(DYLIB_INSTALL_NAME "/usr/lib/darling/libelfloader.dylib") -add_darling_library(elfloader SHARED ${elfloader_sources}) -target_link_libraries(elfloader system) -make_fat(elfloader) - -install(TARGETS elfloader DESTINATION libexec/darling/usr/lib/darling) - diff --git a/src/libelfloader/auxvec.h b/src/libelfloader/auxvec.h deleted file mode 100644 index c5e1a3229..000000000 --- a/src/libelfloader/auxvec.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _UAPI_LINUX_AUXVEC_H -#define _UAPI_LINUX_AUXVEC_H - -/* Symbolic values for the entries in the auxiliary table - put on the initial stack */ -#define AT_NULL 0 /* end of vector */ -#define AT_IGNORE 1 /* entry should be ignored */ -#define AT_EXECFD 2 /* file descriptor of program */ -#define AT_PHDR 3 /* program headers for program */ -#define AT_PHENT 4 /* size of program header entry */ -#define AT_PHNUM 5 /* number of program headers */ -#define AT_PAGESZ 6 /* system page size */ -#define AT_BASE 7 /* base address of interpreter */ -#define AT_FLAGS 8 /* flags */ -#define AT_ENTRY 9 /* entry point of program */ -#define AT_NOTELF 10 /* program is not ELF */ -#define AT_UID 11 /* real uid */ -#define AT_EUID 12 /* effective uid */ -#define AT_GID 13 /* real gid */ -#define AT_EGID 14 /* effective gid */ -#define AT_PLATFORM 15 /* string identifying CPU for optimizations */ -#define AT_HWCAP 16 /* arch dependent hints at CPU capabilities */ -#define AT_CLKTCK 17 /* frequency at which times() increments */ -/* AT_* values 18 through 22 are reserved */ -#define AT_SECURE 23 /* secure mode boolean */ -#define AT_BASE_PLATFORM 24 /* string identifying real platform, may - * differ from AT_PLATFORM. */ -#define AT_RANDOM 25 /* address of 16 random bytes */ -#define AT_HWCAP2 26 /* extension of AT_HWCAP */ - -#define AT_EXECFN 31 /* filename of program */ - - -#endif /* _UAPI_LINUX_AUXVEC_H */ - diff --git a/src/libelfloader/loader.c b/src/libelfloader/loader.c deleted file mode 100644 index 945d8b939..000000000 --- a/src/libelfloader/loader.c +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Darling - * Copyright (C) 2017 Lubos Dolezel - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "auxvec.h" -#include "loader.h" -#include "native/elfcalls.h" - -#ifdef __APPLE__ -extern int getentropy(void* buf, size_t len); -#endif - -#define ElfW(type) _ElfW (Elf, __ELF_NATIVE_CLASS, type) -#define _ElfW(e,w,t) _ElfW_1 (e, w, _##t) -#define _ElfW_1(e,w,t) e##w##t - -#if defined(__x86_64__) || defined(__arm64__) -# define DUMMY_PATH "/usr/libexec/elfloader_dummy64" -# define MY_ELF_CLASS ELFCLASS64 -# define __ELF_NATIVE_CLASS 64 -#else -# define DUMMY_PATH "/usr/libexec/elfloader_dummy32" -# define MY_ELF_CLASS ELFCLASS32 -# define __ELF_NATIVE_CLASS 32 -#endif - -#define ELF_MIN_ALIGN 0x1000 -#define ELF_PAGESTART(_v) ((_v) & ~(unsigned long)(ELF_MIN_ALIGN-1)) -#define ELF_PAGEOFFSET(_v) ((_v) & (ELF_MIN_ALIGN-1)) -#define ELF_PAGEALIGN(_v) (((_v) + ELF_MIN_ALIGN - 1) & ~(ELF_MIN_ALIGN - 1)) - -struct loader_context -{ - uintptr_t interp_entry; - uintptr_t exec_entry; - uintptr_t exec_phstart; - uintptr_t exec_phentsize; - uintptr_t exec_phnum; - uintptr_t interp_base; -}; - -static void run(const char* path, const char** envp); -static void load(const char* path, struct loader_context* lc, bool isInterp); -static void loader_return(void); -static const char SYSTEM_ROOT[] = "/Volumes/SystemRoot"; - -static jmp_buf jmpbuf; -struct elf_calls* _elfcalls; - -#ifndef TEST -__attribute__((constructor)) - static void runDummy(int argc, const char** argv, const char** envp) -{ - _elfcalls = (struct elf_calls*) malloc(sizeof(struct elf_calls)); - run(DUMMY_PATH, envp); -} -#endif - -static unsigned long processEnvVariable(const char* var) -{ - if (strncmp(var, "HOME=", 5) == 0) - { - char* home = (char*) malloc(512); - char* symlink_path = (char*) malloc(strlen(var+5) + 10); - - strcpy(symlink_path, var+5); - strcat(symlink_path, "/LinuxHome"); - - int len = readlink(symlink_path, home, 512-1); - free(symlink_path); - - if (len < 0) - { - free(home); - goto out; - } - home[len] = '\0'; - - if (strncmp(home, SYSTEM_ROOT, sizeof(SYSTEM_ROOT) - 1) != 0) - { - free(home); - goto out; - } - - strcpy(home, "HOME="); - memmove(home+5, home + sizeof(SYSTEM_ROOT) - 1, len - sizeof(SYSTEM_ROOT) + 2); - - return (unsigned long) home; - } -out: - return (unsigned long) var; -} - -void run(const char* path, const char** envp) -{ - struct loader_context lc; - unsigned long *stack, *stackmem; - int ptrcount, pos = 0; - uint8_t entropy[16]; - char pointer1[20]; - char pointer2[20]; - - load(path, &lc, false); - if (!lc.interp_entry) - return; - - stackmem = (unsigned long*) mmap(NULL, 4096*4, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANON, -1, 0); - - if (stackmem == MAP_FAILED) - perror("mmap"); - - stack = (unsigned long*) (((char*) stackmem) + 4096*4 - sizeof(unsigned long)); - - // AT_* - ptrcount = 10*2; - ptrcount++; // argc - ptrcount += 4; // argv - - // environ - for (int i = 0; envp[i] != NULL; i++) - ptrcount++; - ptrcount++; - - // Ensure 16-byte alignment - if (ptrcount % 2 == 0) - ptrcount++; - - stack -= ptrcount; - - stack[pos++] = 3; // argc - stack[pos++] = (unsigned long) "elfloader_dummy"; - - snprintf(pointer1, sizeof(pointer1), "%lx", (unsigned long) _elfcalls); - stack[pos++] = (unsigned long) pointer1; - - snprintf(pointer2, sizeof(pointer2), "%lx", (unsigned long) loader_return); - stack[pos++] = (unsigned long) pointer2; - stack[pos++] = 0; - - - for (int i = 0; envp[i] != NULL; i++) - stack[pos++] = processEnvVariable(envp[i]); - -#ifdef __linux__ // For testing - getrandom(entropy, sizeof(entropy), 0); -#else // Darwin - getentropy(entropy, sizeof(entropy)); -#endif - - stack[pos++] = 0; - - stack[pos++] = AT_PHDR; - stack[pos++] = lc.exec_phstart; - - stack[pos++] = AT_PHENT; - stack[pos++] = lc.exec_phentsize; - - stack[pos++] = AT_PHNUM; - stack[pos++] = lc.exec_phnum; - - stack[pos++] = AT_ENTRY; - stack[pos++] = lc.exec_entry; - - stack[pos++] = AT_BASE; - stack[pos++] = lc.interp_base; - - stack[pos++] = AT_PAGESZ; - stack[pos++] = 4096; - - stack[pos++] = AT_FLAGS; - stack[pos++] = 0; - - stack[pos++] = AT_RANDOM; - stack[pos++] = (unsigned long) entropy; - - stack[pos++] = AT_NULL; - stack[pos++] = 0; - - // TODO: AT_EXECFN? -#ifdef __x86_64__ -# define JUMPX(stack, addr) __asm__ volatile("mov %1, %%rsp; jmpq *%0" :: "m"(addr), "r"(stack) :) -#elif defined(__i386__) -# define JUMPX(stack, addr) __asm__ volatile("mov %1, %%esp; jmp *%0" :: "m"(addr), "r"(stack) :) -#else -# error Unsupported platform! -#endif - - if (!setjmp(jmpbuf)) - JUMPX(stack, lc.interp_entry); - else - { - // puts("Back from loaded binary"); - } -} - -// AT_PHDR -> loadaddr + phoff -// AT_PHENT -> e_phentsize -// AT_PHNUM -> e_phnum -// AT_ENTRY -> exec->e_entry -// AT_BASE -> interp_loadaddr -// AT_EXECFN -> apple[0] -// AT_PAGESZ -> PAGE_SIZE -// AT_FLAGS -> 0 -// AT_NULL - -void load(const char* path, struct loader_context* lc, bool isInterp) -{ - ElfW(Ehdr) elfHdr; - void* phdrs = NULL; - - // the interpreter path should be relative to the Linux root, not the Darling prefix - int fd = isInterp ? _open_for_libelfloader(path, O_RDONLY, 0) : open(path, O_RDONLY); - uintptr_t slide, base; - - if (fd == -1) - { - perror("open"); - return; - } - - if (read(fd, &elfHdr, sizeof(elfHdr)) != sizeof(elfHdr)) - { - perror("read"); - goto out; - } - - if (memcmp(elfHdr.e_ident, ELFMAG, SELFMAG) != 0 || elfHdr.e_ident[EI_CLASS] != MY_ELF_CLASS) - { - fprintf(stderr, "Wrong ELF signature\n"); - goto out; - } - - if (elfHdr.e_type != ET_DYN) - { - fprintf(stderr, "Only position independent ELF are supported\n"); - goto out; - } - if (!elfHdr.e_phoff) - { - fprintf(stderr, "ELF is not loadable\n"); - goto out; - } - - phdrs = malloc(elfHdr.e_phentsize * elfHdr.e_phnum); - if (pread(fd, phdrs, elfHdr.e_phentsize * elfHdr.e_phnum, elfHdr.e_phoff) != elfHdr.e_phentsize * elfHdr.e_phnum) - { - fprintf(stderr, "Failed to read Elf phdrs\n"); - goto out; - } - - // First, get total virtual range needed - uintptr_t minAddr = UINTPTR_MAX, maxAddr = 0; - - for (int i = 0; i < elfHdr.e_phnum; i++) - { - ElfW(Phdr)* phdr = (ElfW(Phdr)*) (((char*) phdrs) + (i * elfHdr.e_phentsize)); - - if (phdr->p_type == PT_LOAD) - { - if (phdr->p_vaddr < minAddr) - minAddr = ELF_PAGESTART(phdr->p_vaddr); - if (phdr->p_vaddr + phdr->p_memsz > maxAddr) - maxAddr = phdr->p_vaddr + phdr->p_memsz; - } - else if (phdr->p_type == PT_INTERP && isInterp) - { - fprintf(stderr, "Interp with PT_INTERP?\n"); - goto out; - } - } - if (maxAddr == 0) - { - fprintf(stderr, "No PT_LOAD headers?\n"); - goto out; - } - - base = (uintptr_t) mmap(NULL, maxAddr-minAddr, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (base == (uintptr_t) MAP_FAILED) - { - perror("mmap"); - fprintf(stderr, "Cannot reserve 0x%lx bytes in memory\n", maxAddr-minAddr); - goto out; - } - slide = base - minAddr; - - for (int i = 0; i < elfHdr.e_phnum; i++) - { - ElfW(Phdr)* phdr = (ElfW(Phdr)*) (((char*) phdrs) + (i * elfHdr.e_phentsize)); - - if (phdr->p_type == PT_LOAD) - { - int prot = 0; - int flags = MAP_FIXED; - void* result; - - uintptr_t addr = phdr->p_vaddr + slide; - uintptr_t size = phdr->p_filesz + ELF_PAGEOFFSET(phdr->p_vaddr); - uintptr_t memsize = phdr->p_memsz + ELF_PAGEOFFSET(phdr->p_vaddr); - uintptr_t off = phdr->p_offset - ELF_PAGEOFFSET(phdr->p_vaddr); - - addr = ELF_PAGESTART(addr); - // size = ELF_PAGEALIGN(size); - - if (phdr->p_flags & PF_X) - prot |= PROT_EXEC; - if (phdr->p_flags & PF_W) - prot |= PROT_WRITE; - if (phdr->p_flags & PF_R) - prot |= PROT_READ; - - //if (phdr->p_flags & PF_W) - flags |= MAP_PRIVATE; - //else - // flags |= MAP_SHARED; - - bool needszeroing = size != ELF_PAGEALIGN(size); - - if (needszeroing) - prot |= PROT_WRITE; - if (mprotect((void*) (addr), memsize, prot) == -1) - { - perror("mprotect"); - } - - result = mmap((void*) addr, size, prot, flags, fd, off); - if (result == MAP_FAILED) - { - perror("mmap"); - goto out; - } - - // Based on experiments, when we provide a size that is less than a multiple of page size - // mmap() will map up to the whole page of file data anyway. Many ELF files, including ld.so, - // however rely on the rest of the page being zeroed out. - if (needszeroing) - { - memset((void*)(addr + size), 0, ELF_PAGEALIGN(size) - size); - if (!(phdr->p_flags & PF_W)) - mprotect((void*) (addr), memsize, prot & ~PROT_WRITE); - } - - /* - if (phdr->p_filesz < phdr->p_memsz) - { - if (mprotect((void*) (addr + phdr->p_filesz), phdr->p_memsz - phdr->p_filesz, prot) == -1) - { - perror("mprotect"); - goto out; - } - } - */ - } - else if (phdr->p_type == PT_INTERP) - { - char* interp = malloc(phdr->p_filesz + 1); - - if (pread(fd, interp, phdr->p_filesz, phdr->p_offset) != phdr->p_filesz) - { - free(interp); - perror("reading PT_INTERP"); - goto out; - } - interp[phdr->p_filesz] = '\0'; - - // Load interpreter - if (_access_for_libelfloader(interp, F_OK) != 0) - { - fprintf(stderr, "Cannot load interpreter at %s\n", interp); - free(interp); - goto out; - } - - load(interp, lc, true); - - free(interp); - } - } - - if (isInterp) - { - lc->interp_base = base; - lc->interp_entry = slide + elfHdr.e_entry; - } - else - { - lc->exec_phstart = slide + elfHdr.e_phoff; - lc->exec_phentsize = elfHdr.e_phentsize; - lc->exec_phnum = elfHdr.e_phnum; - lc->exec_entry = slide + elfHdr.e_entry; - } - -out: - free(phdrs); - close(fd); -} - -void loader_return(void) -{ - longjmp(jmpbuf, 1); -} - -#ifdef TEST - -int main(int argc, const char** argv) -{ - if (argc > 1) - run(argv[1]); - return 0; -} - -#endif - diff --git a/src/libelfloader/loader.h b/src/libelfloader/loader.h deleted file mode 100644 index f56743534..000000000 --- a/src/libelfloader/loader.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Darling - * Copyright (C) 2017 Lubos Dolezel - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef _LOADER_H -#define _LOADER_H -#include "native/elfcalls.h" - -extern struct elf_calls* _elfcalls; - -#endif - - diff --git a/src/libelfloader/native/CMakeLists.txt b/src/libelfloader/native/CMakeLists.txt deleted file mode 100644 index 8e6915226..000000000 --- a/src/libelfloader/native/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -project(libelfstub) - -set(elfstub_sources - elfcalls.c - threads.c -) - -if (BUILD_TARGET_64BIT) - add_executable(elfloader_dummy64 ${elfstub_sources}) - target_link_libraries(elfloader_dummy64 -lpthread -lrt -ldl) - set_target_properties(elfloader_dummy64 PROPERTIES COMPILE_FLAGS "-fPIC" - LINK_FLAGS "-pie -fPIC") -endif () - -if (BUILD_TARGET_32BIT) - add_executable(elfloader_dummy32 ${elfstub_sources}) - target_link_libraries(elfloader_dummy32 -lpthread -lrt -ldl) - set_target_properties(elfloader_dummy32 PROPERTIES COMPILE_FLAGS "-fPIC -m32" - LINK_FLAGS "-pie -fPIC -m32") -endif () - -if (BUILD_TARGET_64BIT AND BUILD_TARGET_32BIT) - install(TARGETS elfloader_dummy32 elfloader_dummy64 DESTINATION libexec/darling/usr/libexec) -elseif (BUILD_TARGET_64BIT) - install(TARGETS elfloader_dummy64 DESTINATION libexec/darling/usr/libexec) -elseif (BUILD_TARGET_32BIT) - install(TARGETS elfloader_dummy32 DESTINATION libexec/darling/usr/libexec) -endif () \ No newline at end of file diff --git a/src/libelfloader/wrapgen/CMakeLists.txt b/src/libelfloader/wrapgen/CMakeLists.txt index 944656d72..58e3cae55 100644 --- a/src/libelfloader/wrapgen/CMakeLists.txt +++ b/src/libelfloader/wrapgen/CMakeLists.txt @@ -10,3 +10,8 @@ endif(COMMAND cmake_policy) add_executable(wrapgen wrapgen.cpp) target_link_libraries(wrapgen dl) +# This tool is useful for packaging to detect ELF dependencies inside Mach-O libraries, +# which standard distro tools cannot do. +if (DEFINED WITH_PRINT_WRAPPED_ELF) + add_executable(print_wrapped_elf print_wrapped_elf.cpp) +endif (DEFINED WITH_PRINT_WRAPPED_ELF) diff --git a/src/startup/CMakeLists.txt b/src/startup/CMakeLists.txt index 544182d8b..95019ec67 100644 --- a/src/startup/CMakeLists.txt +++ b/src/startup/CMakeLists.txt @@ -18,7 +18,7 @@ add_executable(darling darling.c) target_link_libraries(darling -lutil) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/src/darlingserver/include) install(TARGETS darling DESTINATION bin PERMISSIONS @@ -27,14 +27,8 @@ install(TARGETS darling DESTINATION bin WORLD_READ WORLD_EXECUTE SETUID) - -# This tool is useful for packaging to detect ELF dependencies inside Mach-O libraries, -# which standard distro tools cannot do. -if (DEFINED WITH_PRINT_WRAPPED_ELF) - add_executable(print_wrapped_elf wrapgen/print_wrapped_elf.cpp) -endif (DEFINED WITH_PRINT_WRAPPED_ELF) - add_executable(rtsig rtsig.c) add_custom_command(OUTPUT rtsig.h DEPENDS rtsig COMMAND ${CMAKE_CURRENT_BINARY_DIR}/rtsig rtsig.h COMMENT "Determining available RT signals") add_custom_target(rtsig_h DEPENDS rtsig.h) +add_subdirectory(mldr) diff --git a/src/startup/darling.c b/src/startup/darling.c index 010a34786..9470e9636 100644 --- a/src/startup/darling.c +++ b/src/startup/darling.c @@ -88,9 +88,6 @@ int main(int argc, char ** argv, char ** envp) setuid(0); setgid(0); - if (!isModuleLoaded()) - loadKernelModule(); - prefix = getenv("DPREFIX"); if (!prefix) prefix = defaultPrefixPath(); @@ -688,39 +685,6 @@ void missingSetuidRoot(void) fprintf(stderr, "Darling needs this in order to create mount and PID namespaces and to perform mounts.\n"); } -void fixDirectoryPermissions(const char* path) -{ - DIR* dir; - struct dirent* ent; - - dir = opendir(path); - if (!dir) - return; - - while ((ent = readdir(dir)) != NULL) - { - if (ent->d_type == DT_DIR) - { - char* subdir; - - if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) - continue; - - subdir = (char*) malloc(strlen(path) + 2 + strlen(ent->d_name)); - sprintf(subdir, "%s/%s", path, ent->d_name); - - fixDirectoryPermissions(subdir); - - if (chown(subdir, g_originalUid, g_originalGid) == -1) - fprintf(stderr, "Cannot chown %s: %s\n", subdir, strerror(errno)); - - free(subdir); - } - } - - closedir(dir); -} - static uint32_t linux_release(void) { struct utsname uts; @@ -763,9 +727,9 @@ pid_t spawnInitProcess(void) exit(1); } - if (unshare(CLONE_NEWPID | CLONE_NEWUTS | CLONE_NEWIPC) != 0) + if (unshare(CLONE_NEWUTS | CLONE_NEWIPC) != 0) { - fprintf(stderr, "Cannot unshare PID, UTS and IPC namespaces to create darling-init: %s\n", strerror(errno)); + fprintf(stderr, "Cannot unshare UTS and IPC namespaces to create darling-init: %s\n", strerror(errno)); exit(1); } @@ -781,96 +745,20 @@ pid_t spawnInitProcess(void) { // The child - char *opts; - char putOld[4096]; - char *p; - - close(pipefd[0]); - - // Since overlay cannot be mounted inside user namespaces, we have to setup a new mount namespace - // and do the mount while we can be root - if (unshare(CLONE_NEWNS) != 0) - { - fprintf(stderr, "Cannot unshare mount namespace: %s\n", strerror(errno)); - exit(1); - } - - // Because systemd marks / as MS_SHARED and we would inherit this into the overlay mount, - // causing it not to be unmounted once the init process dies. - if (mount(NULL, "/", NULL, MS_REC | MS_SLAVE, NULL) != 0) - { - fprintf(stderr, "Cannot remount / as slave: %s\n", strerror(errno)); - exit(1); - } - - umount("/dev/shm"); - if (mount("tmpfs", "/dev/shm", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) != 0) - { - fprintf(stderr, "Cannot mount new /dev/shm: %s\n", strerror(errno)); - exit(1); - } - - opts = (char*) malloc(strlen(prefix)*2 + sizeof(LIBEXEC_PATH) + 100); - - const char* opts_fmt = "lowerdir=%s,upperdir=%s,workdir=%s.workdir,index=off"; - - sprintf(opts, opts_fmt, LIBEXEC_PATH, prefix, prefix); - - // Mount overlay onto our prefix - if (mount("overlay", prefix, "overlay", 0, opts) != 0) - { - fprintf(stderr, "Cannot mount overlay: %s\n", strerror(errno)); - exit(1); - } - - free(opts); - - // This is executed once at prefix creation - if (g_fixPermissions) - fixDirectoryPermissions(prefix); - - snprintf(putOld, sizeof(putOld), "%s/proc", prefix); - - // mount procfs for our new PID namespace - if (mount("proc", putOld, "proc", 0, "") != 0) - { - fprintf(stderr, "Cannot mount procfs: %s\n", strerror(errno)); - exit(1); - } + char uid_str[21]; + char gid_str[21]; + char pipefd_str[21]; - // Drop the privileges. It's important to drop GID first, because - // non-root users can't change their GID. - setresgid(g_originalGid, g_originalGid, g_originalGid); - setresuid(g_originalUid, g_originalUid, g_originalUid); - prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + snprintf(uid_str, sizeof(uid_str), "%d", g_originalUid); + snprintf(gid_str, sizeof(gid_str), "%d", g_originalGid); + snprintf(pipefd_str, sizeof(pipefd_str), "%d", pipefd[1]); - setupUserHome(); - setupCoredumpPattern(); - - // Set name to darling-init - prctl(PR_SET_NAME, DARLING_INIT_COMM, 0, 0); - p = stpcpy(g_argv[0], DARLING_INIT_COMM); - memset(p, 0, g_envp[0] - p); - - /* - if (unshare(CLONE_NEWUSER) != 0) - { - fprintf(stderr, "Cannot unshare user namespace: %s\n", strerror(errno)); - exit(1); - } - */ - - // Tell the parent we're ready - write(pipefd[1], buffer, 1); - close(pipefd[1]); - - // Here's where we wait for the parent to set up UID/GID mapping - // if we enable user namespaces + close(pipefd[0]); - darlingPreInit(); - spawnLaunchd(); + execl(INSTALL_PREFIX "/bin/darlingserver", "darlingserver", prefix, uid_str, gid_str, pipefd_str, g_fixPermissions ? "1" : "0", NULL); - // Never returns + fprintf(stderr, "Failed to start darlingserver\n"); + exit(1); } // Wait for the child to drop UID/GIDs and unshare stuff @@ -939,67 +827,6 @@ void putInitPid(pid_t pidInit) fclose(fp); } -void spawnLaunchd(void) -{ - puts("Bootstrapping the container with launchd..."); - - // putenv("KQUEUE_DEBUG=1"); - - setenv("DYLD_ROOT_PATH", LIBEXEC_PATH, 1); - execl(LIBEXEC_PATH "/usr/libexec/darling/vchroot", "vchroot", prefix, "/sbin/launchd", NULL); - - fprintf(stderr, "Failed to exec launchd: %s\n", strerror(errno)); - abort(); -} - -static void wipeDir(const char* dirpath) -{ - char path[4096]; - struct dirent* ent; - DIR* dir = opendir(dirpath); - - if (!dir) - return; - - while ((ent = readdir(dir)) != NULL) - { - if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) - continue; - - snprintf(path, sizeof(path), "%s/%s", dirpath, ent->d_name); - - if (ent->d_type == DT_DIR) - { - wipeDir(path); - rmdir(path); - } - else - unlink(path); - } - - closedir(dir); -} - -void darlingPreInit(void) -{ - // TODO: Run /usr/libexec/makewhatis - const char* dirs[] = { - "/var/tmp", - "/var/run" - }; - - char fullpath[4096]; - strcpy(fullpath, prefix); - const size_t prefixLen = strlen(fullpath); - - for (size_t i = 0; i < sizeof(dirs)/sizeof(dirs[0]); i++) - { - fullpath[prefixLen] = 0; - strcat(fullpath, dirs[i]); - wipeDir(fullpath); - } -} - char* defaultPrefixPath(void) { const char defaultPath[] = "/.darling"; @@ -1320,168 +1147,3 @@ void checkPrefixOwner() exit(1); } } - -int isModuleLoaded() -{ - size_t len = 0; - ssize_t read = 0; - char * line = NULL; - FILE *fp = NULL; - - if ((fp = fopen("/proc/modules", "r")) == NULL) - { - fprintf(stderr, "Failure opening /proc/modules: %s\n", strerror(errno)); - fclose(fp); - return 0; - } - - while (!feof(fp)) - { - read = getline(&line, &len, fp); - if (read > 0 && strstr(line, "darling_mach") != NULL) - { - fclose(fp); - return 1; - } - } - - fclose(fp); - return 0; -} - -void loadKernelModule() -{ - int status; - FILE *fp = popen("/sbin/modprobe darling-mach", "w"); - - if (fp == NULL) - { - fprintf(stderr, "Failed to run modprobe: %s\n", strerror(errno)); - exit(1); - } - - status = pclose(fp); - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) - { - fprintf(stderr, "Loaded the kernel module\n"); - return; - } - else - { - fprintf(stderr, "Failed to load the kernel module\n"); - exit(1); - } -} - -void setupCoredumpPattern(void) -{ - FILE* f = fopen("/proc/sys/kernel/core_pattern", "w"); - if (f != NULL) - { - // This is how macOS saves core dumps - fputs("/cores/core.%p\n", f); - fclose(f); - } -} - -const char* xdgDirectory(const char* name) -{ - static char dir[4096]; - char* cmd = (char*) malloc(16 + strlen(name)); - - sprintf(cmd, "xdg-user-dir %s", name); - - FILE* proc = popen(cmd, "r"); - - free(cmd); - - if (!proc) - return NULL; - - fgets(dir, sizeof(dir)-1, proc); - - pclose(proc); - - size_t len = strlen(dir); - if (len <= 1) - return NULL; - - if (dir[len-1] == '\n') - dir[len-1] = '\0'; - return dir; -} - -void setupUserHome(void) -{ - char buf[4096], buf2[4096]; - - snprintf(buf, sizeof(buf), "%s/Users", prefix); - - // Remove the old /Users symlink that may exist - unlink(buf); - - // mkdir /Users - mkdir(buf, 0777); - - // mkdir /Users/Shared - strcat(buf, "/Shared"); - mkdir(buf, 0777); - - const char* home = getenv("HOME"); - - const char* login = NULL; - struct passwd* pw = getpwuid(getuid()); - - if (pw != NULL) - login = pw->pw_name; - - if (!login) - login = getlogin(); - - if (!login) - { - fprintf(stderr, "Cannot determine your user name\n"); - exit(1); - } - if (!home) - { - fprintf(stderr, "Cannot determine your home directory\n"); - exit(1); - } - - snprintf(buf, sizeof(buf), "%s/Users/%s", prefix, login); - - // mkdir /Users/$LOGIN - mkdir(buf, 0755); - - snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", home); - - strcat(buf, "/LinuxHome"); - unlink(buf); - - // symlink /Users/$LOGIN/LinuxHome -> $HOME - symlink(buf2, buf); - - static const char* xdgmap[][2] = { - { "DESKTOP", "Desktop" }, - { "DOWNLOAD", "Downloads" }, - { "PUBLICSHARE", "Public" }, - { "DOCUMENTS", "Documents" }, - { "MUSIC", "Music" }, - { "PICTURES", "Pictures" }, - { "VIDEOS", "Movies" }, - }; - - for (int i = 0; i < sizeof(xdgmap) / sizeof(xdgmap[0]); i++) - { - const char* dir = xdgDirectory(xdgmap[i][0]); - if (!dir) - continue; - - snprintf(buf2, sizeof(buf2), "/Volumes/SystemRoot%s", dir); - snprintf(buf, sizeof(buf), "%s/Users/%s/%s", prefix, login, xdgmap[i][1]); - - unlink(buf); - symlink(buf2, buf); - } -} diff --git a/src/startup/dirstructure.cpp b/src/startup/dirstructure.cpp deleted file mode 100644 index 1948f5ea5..000000000 --- a/src/startup/dirstructure.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* -This file is part of Darling. - -Copyright (C) 2015 Lubos Dolezel - -Darling is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Darling is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Darling. If not, see . -*/ - -#include "dirstructure.h" -#include -#include -#include -#include -#include -#include -#include -#include - -static std::string GetUserLibrary() -{ - const char *home, *prefix; - std::stringstream ss; - std::string path; - - home = getenv("HOME"); - if (!home) - return std::string(); // give up on this user - - ss << home << '/' << "Library" << '/'; - return ss.str(); -} - -bool HasUserDirectoryStructure() -{ - std::string path = GetUserLibrary(); - - if (path.empty()) - return true; // give up on this user - - if (::access(path.c_str(), F_OK) == -1) - return false; - else - return true; -} - -void SetupUserDirectoryStructure() -{ - const char* dirs[] = { - "Application Support", - "Assistants", - "Audio", - "Audio/MIDI Drivers", - "Audio/Plug-Ins", - "Audio/Plug-Ins/Components", - "Audio/Plug-Ins/Digidesign", - "Audio/Plug-Ins/VST", - "Audio/Sounds", - "Audio/Sounds/Alerts", - "Audio/Sounds/Banks", - "Caches", - "ColorPickers", - "Colors", - "Compositions", - "Favorites", - "FontCollections", - "Fonts", - "iMovie", - "iMovie/Plug-ins", - "iMovie/Sound Effects", - "Input Methods", - "Internet Plug-Ins", - "Keyboard Layouts", - "Logs", - "PreferencePanes", - "Preferences", - "Printers", - "Screen Savers", - "Sounds", - "Spelling", - "Voices", - }; - - std::string path = GetUserLibrary(); - - if (path.empty()) - return; - - std::cerr << "Darling: Creating Library structure at " << path << std::endl; - - if (::mkdir(path.c_str(), 0777) == -1) - std::cerr << "Darling: Cannot mkdir(" << path << "): " << strerror(errno) << std::endl; - - for (const char* dir : dirs) - { - std::string s = path; - - s += dir; - - if (::mkdir(s.c_str(), 0777) == -1) - std::cerr << "Darling: Cannot mkdir(" << s << "): " << strerror(errno) << std::endl; - } -} diff --git a/src/startup/dirstructure.h b/src/startup/dirstructure.h deleted file mode 100644 index d40963b55..000000000 --- a/src/startup/dirstructure.h +++ /dev/null @@ -1,29 +0,0 @@ -/* -This file is part of Darling. - -Copyright (C) 2015 Lubos Dolezel - -Darling is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Darling is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Darling. If not, see . -*/ - -#ifndef DIRSTRUCTURE_H -#define DIRSTRUCTURE_H - -// Checks whether $HOME/Library exists -bool HasUserDirectoryStructure(); - -// Creates the default OS X $HOME/Library directory structure -void SetupUserDirectoryStructure(); - -#endif diff --git a/src/startup/mldr/CMakeLists.txt b/src/startup/mldr/CMakeLists.txt new file mode 100644 index 000000000..acc0efe33 --- /dev/null +++ b/src/startup/mldr/CMakeLists.txt @@ -0,0 +1,47 @@ +project(mldr) + +cmake_minimum_required(VERSION 3.10) + +include_directories(include) + +add_library(mldr_dserver_rpc ${CMAKE_BINARY_DIR}/src/darlingserver/src/rpc.c) +add_library(mldr32_dserver_rpc ${CMAKE_BINARY_DIR}/src/darlingserver/src/rpc.c) + +add_dependencies(mldr_dserver_rpc generate_dserver_rpc_wrappers) +add_dependencies(mldr32_dserver_rpc generate_dserver_rpc_wrappers) + +target_compile_options(mldr_dserver_rpc PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/resources/dserver-rpc-defs.h) +target_compile_options(mldr32_dserver_rpc PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/resources/dserver-rpc-defs.h) + +target_compile_options(mldr32_dserver_rpc PRIVATE -m32) +target_link_options(mldr32_dserver_rpc PRIVATE -m32) + +set(mldr_sources + mldr.c + commpage.c + elfcalls/elfcalls.c + elfcalls/threads.c +) + +add_executable(mldr ${mldr_sources}) + +add_executable(mldr32 ${mldr_sources}) +target_compile_options(mldr32 PRIVATE -m32) +target_link_options(mldr32 PRIVATE -m32) + +target_compile_options(mldr PRIVATE -pthread) +target_compile_options(mldr32 PRIVATE -pthread) +target_link_options(mldr PRIVATE -pthread) +target_link_options(mldr32 PRIVATE -pthread) + +target_link_libraries(mldr PRIVATE -lrt -ldl mldr_dserver_rpc) +target_link_libraries(mldr32 PRIVATE -lrt -ldl mldr32_dserver_rpc) + +install(TARGETS mldr mldr32 DESTINATION libexec/darling/usr/libexec/darling) + +include(setcap) +setcap(libexec/darling/usr/libexec/darling/mldr cap_sys_rawio,cap_sys_resource+ep) +setcap(libexec/darling/usr/libexec/darling/mldr32 cap_sys_rawio,cap_sys_resource+ep) + +configure_file(darling.conf.in darling.conf @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/darling.conf" DESTINATION lib/binfmt.d) diff --git a/src/startup/mldr/commpage.c b/src/startup/mldr/commpage.c new file mode 100644 index 000000000..5c45ff246 --- /dev/null +++ b/src/startup/mldr/commpage.c @@ -0,0 +1,151 @@ +#include "commpage.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// Include commpage definitions +#define PRIVATE +#include + +static const char* SIGNATURE32 = "commpage 32-bit"; +static const char* SIGNATURE64 = "commpage 64-bit"; + +static uint64_t get_cpu_caps(void); + +#define CGET(p) (commpage + ((p)-_COMM_PAGE_START_ADDRESS)) + +void commpage_setup(bool _64bit) +{ + uint8_t* commpage; + uint64_t* cpu_caps64; + uint32_t* cpu_caps; + uint16_t* version; + char* signature; + uint64_t my_caps; + uint8_t *ncpus, *nactivecpus; + uint8_t *physcpus, *logcpus; + struct sysinfo si; + + commpage = (uint8_t*) mmap((void*)(_64bit ? _COMM_PAGE64_BASE_ADDRESS : _COMM_PAGE32_BASE_ADDRESS), + _64bit ? _COMM_PAGE64_AREA_LENGTH : _COMM_PAGE32_AREA_LENGTH, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if (commpage == MAP_FAILED) + { + fprintf(stderr, "Cannot mmap commpage: %s\n", strerror(errno)); + exit(1); + } + + signature = (char*)CGET(_COMM_PAGE_SIGNATURE); + version = (uint16_t*)CGET(_COMM_PAGE_VERSION); + cpu_caps64 = (uint64_t*)CGET(_COMM_PAGE_CPU_CAPABILITIES64); + cpu_caps = (uint32_t*)CGET(_COMM_PAGE_CPU_CAPABILITIES); + + strcpy(signature, _64bit ? SIGNATURE64 : SIGNATURE32); + *version = _COMM_PAGE_THIS_VERSION; + + ncpus = (uint8_t*)CGET(_COMM_PAGE_NCPUS); + *ncpus = sysconf(_SC_NPROCESSORS_CONF); + + nactivecpus = (uint8_t*)CGET(_COMM_PAGE_ACTIVE_CPUS); + *nactivecpus = sysconf(_SC_NPROCESSORS_ONLN); + + // Better imprecise information than no information + physcpus = (uint8_t*)CGET(_COMM_PAGE_PHYSICAL_CPUS); + logcpus = (uint8_t*)CGET(_COMM_PAGE_LOGICAL_CPUS); + *physcpus = *logcpus = *ncpus; + + my_caps = get_cpu_caps(); + if (*ncpus == 1) + my_caps |= kUP; + + *cpu_caps = (uint32_t) my_caps; + *cpu_caps64 = my_caps; + + if (sysinfo(&si) == 0) + { + uint64_t* memsize = (uint64_t*)CGET(_COMM_PAGE_MEMORY_SIZE); + *memsize = si.totalram * si.mem_unit; + } +} + +uint64_t get_cpu_caps(void) +{ + uint64_t caps = 0; + + { + union cpu_flags1 eax; + union cpu_flags2 ecx; + union cpu_flags3 edx; + uint32_t ebx; + + eax.reg = ecx.reg = edx.reg = 0; + __cpuid(1, eax.reg, ebx, ecx.reg, edx.reg); + + if (ecx.mmx) + caps |= kHasMMX; + if (ecx.sse) + caps |= kHasSSE; + if (ecx.sse2) + caps |= kHasSSE2; + if (edx.sse3) + caps |= kHasSSE3; + if (ecx.ia64) + caps |= k64Bit; + if (edx.ssse3) + caps |= kHasSupplementalSSE3; + if (edx.sse41) + caps |= kHasSSE4_1; + if (edx.sse42) + caps |= kHasSSE4_2; + if (edx.aes) + caps |= kHasAES; + if (edx.avx) + caps |= kHasAVX1_0; + if (edx.rdrnd) + caps |= kHasRDRAND; + if (edx.fma) + caps |= kHasFMA; + if (edx.f16c) + caps |= kHasF16C; + } + { + union cpu_flags4 ebx; + union cpu_flags5 ecx; + uint32_t edx = 0, eax = 7; + + __cpuid(7, eax, ebx.reg, ecx.reg, edx); + + if (ebx.erms) + caps |= kHasENFSTRG; + if (ebx.avx2) + caps |= kHasAVX2_0; + if (ebx.bmi1) + caps |= kHasBMI1; + if (ebx.bmi2) + caps |= kHasBMI2; + if (ebx.rtm) + caps |= kHasRTM; + if (ebx.hle) + caps |= kHasHLE; + if (ebx.rdseed) + caps |= kHasRDSEED; + if (ebx.adx) + caps |= kHasADX; + if (ebx.mpx) + caps |= kHasMPX; + if (ebx.sgx) + caps |= kHasSGX; + } + + return caps; +} + +unsigned long commpage_address(bool _64bit) +{ + return _64bit ? _COMM_PAGE64_BASE_ADDRESS : _COMM_PAGE32_BASE_ADDRESS; +} + diff --git a/src/startup/mldr/commpage.h b/src/startup/mldr/commpage.h new file mode 100644 index 000000000..4d9558c72 --- /dev/null +++ b/src/startup/mldr/commpage.h @@ -0,0 +1,155 @@ +#ifndef _COMMPAGE_H +#define _COMMPAGE_H +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void commpage_setup(bool _64bit); +unsigned long commpage_address(bool _64bit); + +union cpu_flags1 { + struct { + uint8_t step: 4; + uint8_t model: 4; + uint8_t family: 4; + uint8_t type: 2; + uint8_t pad1: 2; + uint8_t emodel: 4; + uint8_t efamily: 8; + uint8_t pad2: 4; + }; + uint32_t reg; +}; + +union cpu_flags2 { + struct { + uint8_t fpu: 1; + uint8_t vme: 1; + uint8_t de: 1; + uint8_t pse: 1; + uint8_t tsc: 1; + uint8_t msr: 1; + uint8_t pae: 1; + uint8_t mce: 1; + uint8_t cx8: 1; + uint8_t apic: 1; + uint8_t reserved1: 1; + uint8_t sep: 1; + uint8_t mtrr: 1; + uint8_t pge: 1; + uint8_t mca: 1; + uint8_t cmov: 1; + uint8_t pat: 1; + uint8_t pse36: 1; + uint8_t psn: 1; + uint8_t clfsh: 1; + uint8_t reserved2: 1; + uint8_t ds: 1; + uint8_t acpi: 1; + uint8_t mmx: 1; + uint8_t fxsr: 1; + uint8_t sse: 1; + uint8_t sse2: 1; + uint8_t ss: 1; + uint8_t htt: 1; + uint8_t tm: 1; + uint8_t ia64: 1; + uint8_t pbe: 1; + }; + uint32_t reg; +}; + +union cpu_flags3 { + struct { + uint32_t sse3: 1; + uint32_t pclmulqdq: 1; + uint32_t dtes64: 1; + uint32_t monitor: 1; + uint32_t dscpl: 1; + uint32_t vmx: 1; + uint32_t smx: 1; + uint32_t est: 1; + uint32_t tm2: 1; + uint32_t ssse3: 1; + uint32_t cntxid: 1; + uint32_t sdbg: 1; + uint32_t fma: 1; + uint32_t cx16: 1; + uint32_t xtpr: 1; + uint32_t pdcm: 1; + uint32_t reserved1: 1; + uint32_t pcid: 1; + uint32_t dca: 1; + uint32_t sse41: 1; + uint32_t sse42: 1; + uint32_t x2apic: 1; + uint32_t movbe: 1; + uint32_t popcnt: 1; + uint32_t tscdadline: 1; + uint32_t aes: 1; + uint32_t xsave: 1; + uint32_t osxsave: 1; + uint32_t avx: 1; + uint32_t f16c: 1; + uint32_t rdrnd: 1; + uint32_t hypervisor: 1; + }; + uint32_t reg; +}; + +union cpu_flags4 { + struct { + uint8_t fsgsbase: 1; + uint8_t ia32tscadjust: 1; + uint8_t sgx: 1; + uint8_t bmi1: 1; + uint8_t hle: 1; + uint8_t avx2: 1; + uint8_t reserved1: 1; + uint8_t smep: 1; + uint8_t bmi2: 1; + uint8_t erms: 1; + uint8_t invpcid: 1; + uint8_t rtm: 1; + uint8_t pqm: 1; + uint8_t fpucsdsdeprecated: 1; + uint8_t mpx: 1; + uint8_t pqe: 1; + uint8_t avx512f: 1; + uint8_t avx512dq: 1; + uint8_t rdseed: 1; + uint8_t adx: 1; + uint8_t smap: 1; + uint8_t avx512ifma: 1; + uint8_t pcommit: 1; + uint8_t clflushopt: 1; + uint8_t clwb: 1; + uint8_t intelproctrace: 1; + uint8_t avx512pf: 1; + uint8_t avx512er: 1; + uint8_t avx512cd: 1; + uint8_t sha: 1; + uint8_t avx512bw: 1; + uint8_t avx512vl: 1; + }; + uint32_t reg; +}; + +union cpu_flags5 { + struct { + uint8_t prefetchwt1: 1; + uint8_t avx512vbmi: 1; + uint32_t reserved: 30; + }; + uint32_t reg; +}; + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/startup/mldr/darling.conf.in b/src/startup/mldr/darling.conf.in new file mode 100644 index 000000000..d3f1c3824 --- /dev/null +++ b/src/startup/mldr/darling.conf.in @@ -0,0 +1,3 @@ +:Mach-O-64-bit:M::\xcf\xfa\xed\xfe::@CMAKE_INSTALL_PREFIX@/libexec/darling/bin/mldr:P +:Mach-O-32-bit:M::\xce\xfa\xed\xfe::@CMAKE_INSTALL_PREFIX@/libexec/darling/bin/mldr32:P +:Fat-Mach-O:M::\xca\xfe\xba\xbe::@CMAKE_INSTALL_PREFIX@/libexec/darling/bin/mldr:P diff --git a/src/libelfloader/native/dthreads.h b/src/startup/mldr/elfcalls/dthreads.h similarity index 100% rename from src/libelfloader/native/dthreads.h rename to src/startup/mldr/elfcalls/dthreads.h diff --git a/src/libelfloader/native/elfcalls.c b/src/startup/mldr/elfcalls/elfcalls.c similarity index 78% rename from src/libelfloader/native/elfcalls.c rename to src/startup/mldr/elfcalls/elfcalls.c index afb5a8b61..b5ad83b8e 100644 --- a/src/libelfloader/native/elfcalls.c +++ b/src/startup/mldr/elfcalls/elfcalls.c @@ -8,6 +8,7 @@ #include #include "elfcalls.h" #include "threads.h" +#include static void* dlopen_simple(const char* name) { @@ -51,6 +52,12 @@ static int get_errno(void) return errno; } +extern struct sockaddr_un __dserver_socket_address_data[]; + +static const void* __dserver_socket_address(void) { + return &__dserver_socket_address_data; +}; + void elfcalls_make(struct elf_calls* calls) { calls->dlopen = dlopen_simple; @@ -80,31 +87,6 @@ void elfcalls_make(struct elf_calls* calls) *((void**)&calls->shm_open) = shm_open; *((void**)&calls->shm_unlink) = shm_unlink; -} - -int main(int argc, const char** argv) -{ - typedef void (*retfunc)(void); - - struct elf_calls* calls; - retfunc ret; - // for (int i = 0; i < argc; i++) - // printf("arg %d: %s\n", i, argv[i]); - - calls = (struct elf_calls*) strtoul(argv[1], NULL, 16); - ret = (retfunc) strtoul(argv[2], NULL, 16); - - // Enable locales - setlocale(LC_ALL, ""); - - // puts("before elfcalls_make"); - - elfcalls_make(calls); - // puts("after elfcalls_make"); - // printf("Will call %p\n", ret); - ret(); - - __builtin_unreachable(); + calls->dserver_socket_address = __dserver_socket_address; } - diff --git a/src/libelfloader/native/elfcalls.h b/src/startup/mldr/elfcalls/elfcalls.h similarity index 95% rename from src/libelfloader/native/elfcalls.h rename to src/startup/mldr/elfcalls/elfcalls.h index 3b9ebbc00..f34293603 100644 --- a/src/libelfloader/native/elfcalls.h +++ b/src/startup/mldr/elfcalls/elfcalls.h @@ -49,6 +49,9 @@ struct elf_calls // POSIX sysconf long (*sysconf)(int name); + + // darlingserver RPC info + const void* (*dserver_socket_address)(void); }; #endif diff --git a/src/libelfloader/native/threads.c b/src/startup/mldr/elfcalls/threads.c similarity index 100% rename from src/libelfloader/native/threads.c rename to src/startup/mldr/elfcalls/threads.c diff --git a/src/libelfloader/native/threads.h b/src/startup/mldr/elfcalls/threads.h similarity index 100% rename from src/libelfloader/native/threads.h rename to src/startup/mldr/elfcalls/threads.h diff --git a/src/startup/mldr/include/architecture/byte_order.h b/src/startup/mldr/include/architecture/byte_order.h new file mode 120000 index 000000000..efebd0a7a --- /dev/null +++ b/src/startup/mldr/include/architecture/byte_order.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/architecture/byte_order.h \ No newline at end of file diff --git a/src/startup/mldr/include/i386/_types.h b/src/startup/mldr/include/i386/_types.h new file mode 100644 index 000000000..4cf4ec764 --- /dev/null +++ b/src/startup/mldr/include/i386/_types.h @@ -0,0 +1,6 @@ +#ifndef _MLDR_I386__TYPES_SHIM_H_ +#define _MLDR_I386__TYPES_SHIM_H_ + +typedef unsigned int __darwin_natural_t; + +#endif // _MLDR_I386__TYPES_SHIM_H_ diff --git a/src/startup/mldr/include/i386/cpu_capabilities.h b/src/startup/mldr/include/i386/cpu_capabilities.h new file mode 120000 index 000000000..9c3140aa6 --- /dev/null +++ b/src/startup/mldr/include/i386/cpu_capabilities.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/i386/cpu_capabilities.h \ No newline at end of file diff --git a/src/startup/mldr/include/libkern/OSByteOrder.h b/src/startup/mldr/include/libkern/OSByteOrder.h new file mode 120000 index 000000000..9ceedf027 --- /dev/null +++ b/src/startup/mldr/include/libkern/OSByteOrder.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/libkern/OSByteOrder.h \ No newline at end of file diff --git a/src/startup/mldr/include/libkern/_OSByteOrder.h b/src/startup/mldr/include/libkern/_OSByteOrder.h new file mode 120000 index 000000000..1341ca0a6 --- /dev/null +++ b/src/startup/mldr/include/libkern/_OSByteOrder.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/libkern/_OSByteOrder.h \ No newline at end of file diff --git a/src/startup/mldr/include/libkern/i386/OSByteOrder.h b/src/startup/mldr/include/libkern/i386/OSByteOrder.h new file mode 120000 index 000000000..f9f2f4427 --- /dev/null +++ b/src/startup/mldr/include/libkern/i386/OSByteOrder.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/libkern/i386/OSByteOrder.h \ No newline at end of file diff --git a/src/startup/mldr/include/libkern/i386/_OSByteOrder.h b/src/startup/mldr/include/libkern/i386/_OSByteOrder.h new file mode 120000 index 000000000..78527ecf7 --- /dev/null +++ b/src/startup/mldr/include/libkern/i386/_OSByteOrder.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/libkern/i386/_OSByteOrder.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach-o b/src/startup/mldr/include/mach-o new file mode 120000 index 000000000..0366ea6cf --- /dev/null +++ b/src/startup/mldr/include/mach-o @@ -0,0 +1 @@ +../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o \ No newline at end of file diff --git a/src/startup/mldr/include/mach/boolean.h b/src/startup/mldr/include/mach/boolean.h new file mode 120000 index 000000000..16055acbc --- /dev/null +++ b/src/startup/mldr/include/mach/boolean.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/boolean.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach/i386/boolean.h b/src/startup/mldr/include/mach/i386/boolean.h new file mode 120000 index 000000000..84112c2c9 --- /dev/null +++ b/src/startup/mldr/include/mach/i386/boolean.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/i386/boolean.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach/i386/vm_param.h b/src/startup/mldr/include/mach/i386/vm_param.h new file mode 100644 index 000000000..d8d322095 --- /dev/null +++ b/src/startup/mldr/include/mach/i386/vm_param.h @@ -0,0 +1,6 @@ +#ifndef _MLDR_MACH_I386_VM_PARAM_SHIM_H_ +#define _MLDR_MACH_I386_VM_PARAM_SHIM_H_ + + + +#endif // _MLDR_MACH_I386_VM_PARAM_SHIM_H_ diff --git a/src/startup/mldr/include/mach/i386/vm_types.h b/src/startup/mldr/include/mach/i386/vm_types.h new file mode 120000 index 000000000..fd1618003 --- /dev/null +++ b/src/startup/mldr/include/mach/i386/vm_types.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/i386/vm_types.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach/machine.h b/src/startup/mldr/include/mach/machine.h new file mode 120000 index 000000000..1fc06aa21 --- /dev/null +++ b/src/startup/mldr/include/mach/machine.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/machine.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach/machine/boolean.h b/src/startup/mldr/include/mach/machine/boolean.h new file mode 120000 index 000000000..6d9646a55 --- /dev/null +++ b/src/startup/mldr/include/mach/machine/boolean.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/machine/boolean.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach/machine/thread_status.h b/src/startup/mldr/include/mach/machine/thread_status.h new file mode 100644 index 000000000..76cf666e9 --- /dev/null +++ b/src/startup/mldr/include/mach/machine/thread_status.h @@ -0,0 +1,6 @@ +#ifndef _MLDR_MACH_MACHINE_THREAD_STATUS_SHIM_H_ +#define _MLDR_MACH_MACHINE_THREAD_STATUS_SHIM_H_ + + + +#endif // _MLDR_MACH_MACHINE_THREAD_STATUS_SHIM_H_ diff --git a/src/startup/mldr/include/mach/machine/vm_types.h b/src/startup/mldr/include/mach/machine/vm_types.h new file mode 120000 index 000000000..fd1618003 --- /dev/null +++ b/src/startup/mldr/include/mach/machine/vm_types.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/i386/vm_types.h \ No newline at end of file diff --git a/src/startup/mldr/include/mach/vm_prot.h b/src/startup/mldr/include/mach/vm_prot.h new file mode 120000 index 000000000..99f521287 --- /dev/null +++ b/src/startup/mldr/include/mach/vm_prot.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach/vm_prot.h \ No newline at end of file diff --git a/src/startup/mldr/include/sys/_types.h b/src/startup/mldr/include/sys/_types.h new file mode 100644 index 000000000..eb510ccf1 --- /dev/null +++ b/src/startup/mldr/include/sys/_types.h @@ -0,0 +1,6 @@ +#ifndef _MLDR_SYS__TYPES_SHIM_H_ +#define _MLDR_SYS__TYPES_SHIM_H_ + + + +#endif // _MLDR_SYS__TYPES_SHIM_H_ diff --git a/src/startup/mldr/include/sys/_types/_os_inline.h b/src/startup/mldr/include/sys/_types/_os_inline.h new file mode 120000 index 000000000..627bd5fe0 --- /dev/null +++ b/src/startup/mldr/include/sys/_types/_os_inline.h @@ -0,0 +1 @@ +../../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/_types/_os_inline.h \ No newline at end of file diff --git a/src/startup/mldr/include/sys/commpage.h b/src/startup/mldr/include/sys/commpage.h new file mode 120000 index 000000000..82ff5172a --- /dev/null +++ b/src/startup/mldr/include/sys/commpage.h @@ -0,0 +1 @@ +../../../../../Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/commpage.h \ No newline at end of file diff --git a/src/startup/mldr/loader.c b/src/startup/mldr/loader.c new file mode 100644 index 000000000..59356a931 --- /dev/null +++ b/src/startup/mldr/loader.c @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "loader.h" + +static int native_prot(int prot); +static void load(const char* path, cpu_type_t cpu, bool expect_dylinker, char** argv, struct load_results* lr); +static void setup_space(struct load_results* lr, bool is_64_bit); + +#ifndef PAGE_SIZE +# define PAGE_SIZE 4096 +#endif +#define PAGE_ALIGN(x) (x & ~(PAGE_SIZE-1)) +#define PAGE_ROUNDUP(x) (((((x)-1) / PAGE_SIZE)+1) * PAGE_SIZE) + +// Definitions: +// FUNCTION_NAME (load32/load64) +// SEGMENT_STRUCT (segment_command/SEGMENT_STRUCT) +// SEGMENT_COMMAND (LC_SEGMENT/SEGMENT_COMMAND) +// MACH_HEADER_STRUCT (mach_header/MACH_HEADER_STRUCT) +// SECTION_STRUCT (section/SECTION_STRUCT) + +#if defined(GEN_64BIT) +# define FUNCTION_NAME load64 +# define SEGMENT_STRUCT segment_command_64 +# define SEGMENT_COMMAND LC_SEGMENT_64 +# define MACH_HEADER_STRUCT mach_header_64 +# define SECTION_STRUCT section_64 +# define MAP_EXTRA 0 +#elif defined(GEN_32BIT) +# define FUNCTION_NAME load32 +# define SEGMENT_STRUCT segment_command +# define SEGMENT_COMMAND LC_SEGMENT +# define MACH_HEADER_STRUCT mach_header +# define SECTION_STRUCT section +# define MAP_EXTRA MAP_32BIT +#else +# error See above +#endif + +void FUNCTION_NAME(int fd, bool expect_dylinker, struct load_results* lr) +{ + struct MACH_HEADER_STRUCT header; + uint8_t* cmds; + uintptr_t entryPoint = 0, entryPointDylinker = 0; + struct MACH_HEADER_STRUCT* mappedHeader = NULL; + uintptr_t slide = 0; + uintptr_t mmapSize = 0; + bool pie = false; + uint32_t fat_offset; + void* tmp_map_base = NULL; + + if (!expect_dylinker) + { +#if defined(GEN_64BIT) + setup_space(lr, true); +#elif defined(GEN_32BIT) + lr->_32on64 = true; + setup_space(lr, false); +#else + #error Unsupported architecture +#endif + } + + fat_offset = lseek(fd, 0, SEEK_CUR); + + if (read(fd, &header, sizeof(header)) != sizeof(header)) + { + fprintf(stderr, "Cannot read the mach header.\n"); + exit(1); + } + + if (header.filetype != (expect_dylinker ? MH_DYLINKER : MH_EXECUTE)) + { + fprintf(stderr, "Found unexpected Mach-O file type: %u\n", header.filetype); + exit(1); + } + + tmp_map_base = mmap(NULL, PAGE_ROUNDUP(sizeof(header) + header.sizeofcmds), PROT_READ, MAP_PRIVATE, fd, fat_offset); + if (tmp_map_base == MAP_FAILED) { + fprintf(stderr, "Failed to mmap header + commands\n"); + exit(1); + } + + cmds = (void*)((char*)tmp_map_base + sizeof(header)); + + if ((header.filetype == MH_EXECUTE && header.flags & MH_PIE) || header.filetype == MH_DYLINKER) + { + uintptr_t base = -1; + + // Go through all SEGMENT_COMMAND commands to get the total continuous range required. + for (uint32_t i = 0, p = 0; i < header.ncmds; i++) + { + struct SEGMENT_STRUCT* seg = (struct SEGMENT_STRUCT*) &cmds[p]; + + // Load commands are always sorted, so this will get us the maximum address. + if (seg->cmd == SEGMENT_COMMAND && strcmp(seg->segname, "__PAGEZERO") != 0) + { + if (base == -1) + { + base = seg->vmaddr; + //if (base != 0 && header.filetype == MH_DYLINKER) + // goto no_slide; + } + mmapSize = seg->vmaddr + seg->vmsize - base; + } + + p += seg->cmdsize; + } + + slide = (uintptr_t) mmap((void*) base, mmapSize, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_EXTRA, -1, 0); + if (slide == (uintptr_t)MAP_FAILED) + { + fprintf(stderr, "Cannot mmap anonymous memory range: %s\n", strerror(errno)); + exit(1); + } + + if (slide + mmapSize > lr->vm_addr_max) + lr->vm_addr_max = lr->base = slide + mmapSize; + slide -= base; + + pie = true; + } +no_slide: + + for (uint32_t i = 0, p = 0; i < header.ncmds && p < header.sizeofcmds; i++) + { + struct load_command* lc; + + lc = (struct load_command*) &cmds[p]; + if (lc->cmdsize > PAGE_SIZE) + { + fprintf(stderr, "Broken Mach-O file, cmdsize = %d\n", lc->cmdsize); + exit(1); + } + + switch (lc->cmd) + { + case SEGMENT_COMMAND: + { + struct SEGMENT_STRUCT* seg = (struct SEGMENT_STRUCT*) lc; + void* rv; + + // This logic is wrong and made up. But it's the only combination where + // some apps stop crashing (TBD why) and LLDB recognized the memory layout + // of processes started as suspended. + int maxprot = native_prot(seg->maxprot); + int initprot = native_prot(seg->initprot); + int useprot = (initprot & PROT_EXEC) ? maxprot : initprot; + + if (seg->filesize < seg->vmsize) + { + unsigned long map_addr; + if (slide != 0) + { + unsigned long addr = seg->vmaddr; + + if (addr != 0) + addr += slide; + + // Some segments' filesize != vmsize, thus this mprotect(). + rv = mmap((void*)addr, seg->vmsize, useprot, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (rv == (void*)MAP_FAILED) + { + fprintf(stderr, "Cannot mmap segment %s at %p: %s\n", seg->segname, (void*)(uintptr_t)seg->vmaddr, strerror(errno)); + exit(1); + } + } + else + { + size_t size = seg->vmsize - seg->filesize; + rv = mmap((void*) PAGE_ALIGN(seg->vmaddr + seg->vmsize - size), PAGE_ROUNDUP(size), useprot, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0); + if (rv == (void*)MAP_FAILED) + { + fprintf(stderr, "Cannot mmap segment %s at %p: %s\n", seg->segname, (void*)(uintptr_t)seg->vmaddr, strerror(errno)); + exit(1); + } + } + } + + if (seg->filesize > 0) + { + unsigned long addr = seg->vmaddr + slide; + rv = mmap((void*)addr, seg->filesize, useprot, + MAP_FIXED | MAP_PRIVATE, fd, seg->fileoff + fat_offset); + if (rv == (void*)MAP_FAILED) + { + fprintf(stderr, "Cannot mmap segment %s at %p: %s\n", seg->segname, (void*)(uintptr_t)seg->vmaddr, strerror(errno)); + exit(1); + } + + if (seg->fileoff == 0) + mappedHeader = (struct MACH_HEADER_STRUCT*) (seg->vmaddr + slide); + } + + if (seg->vmaddr + slide + seg->vmsize > lr->vm_addr_max) + lr->vm_addr_max = seg->vmaddr + slide + seg->vmsize; + + if (strcmp(SEG_DATA, seg->segname) == 0) + { + // Look for section named __all_image_info for GDB integration + struct SECTION_STRUCT* sect = (struct SECTION_STRUCT*) (seg+1); + struct SECTION_STRUCT* end = (struct SECTION_STRUCT*) (&cmds[p + lc->cmdsize]); + + while (sect < end) + { + if (strncmp(sect->sectname, "__all_image_info", 16) == 0) + { + lr->dyld_all_image_location = slide + sect->addr; + lr->dyld_all_image_size = sect->size; + break; + } + sect++; + } + } + break; + } + case LC_UNIXTHREAD: + { +#ifdef GEN_64BIT + entryPoint = ((uint64_t*) lc)[18]; +#endif +#ifdef GEN_32BIT + entryPoint = ((uint32_t*) lc)[14]; +#endif + entryPoint += slide; + break; + } + case LC_LOAD_DYLINKER: + { + if (header.filetype != MH_EXECUTE) + { + // dylinker can't reference another dylinker + fprintf(stderr, "Dynamic linker can't reference another dynamic linker\n"); + exit(1); + } + + struct dylinker_command* dy = (struct dylinker_command*) lc; + char* path; + size_t length; + static char path_buffer[4096]; + + if (lr->root_path != NULL) + { + const size_t root_len = strlen(lr->root_path); + const size_t linker_len = dy->cmdsize - dy->name.offset; + + length = linker_len + root_len; + if (length > sizeof(path_buffer) - 1) { + fprintf(stderr, "Dynamic loader path too long"); + exit(1); + } + path = path_buffer; + + // Concat root path and linker path + memcpy(path, lr->root_path, root_len); + memcpy(path + root_len, ((char*) dy) + dy->name.offset, linker_len); + path[length] = '\0'; + } + + if (path == NULL) + { + length = dy->cmdsize - dy->name.offset; + if (length > sizeof(path_buffer) - 1) { + fprintf(stderr, "Dynamic loader path too long"); + exit(1); + } + path = path_buffer; + + memcpy(path, ((char*) dy) + dy->name.offset, length); + path[length] = '\0'; + } + + if (path == NULL) + { + fprintf(stderr, "Failed to load dynamic linker for executable\n"); + exit(1); + } + + load(path, header.cputype, true, NULL, lr); + + break; + } + case LC_MAIN: + { + struct entry_point_command* ee = (struct entry_point_command*) lc; + if (ee->stacksize > lr->stack_size) + lr->stack_size = ee->stacksize; + break; + } + case LC_UUID: + { + if (header.filetype == MH_EXECUTE) + { + struct uuid_command* ue = (struct uuid_command*) lc; + memcpy(lr->uuid, ue->uuid, sizeof(ue->uuid)); + } + break; + } + } + + p += lc->cmdsize; + } + + if (header.filetype == MH_EXECUTE) + lr->mh = (uintptr_t) mappedHeader; + if (entryPoint && !lr->entry_point) + lr->entry_point = entryPoint; + + if (tmp_map_base) + munmap(tmp_map_base, PAGE_ROUNDUP(sizeof(header) + header.sizeofcmds)); +} + + +#undef FUNCTION_NAME +#undef SEGMENT_STRUCT +#undef SEGMENT_COMMAND +#undef MACH_HEADER_STRUCT +#undef SECTION_STRUCT +#undef MAP_EXTRA + diff --git a/src/startup/mldr/loader.h b/src/startup/mldr/loader.h new file mode 100644 index 000000000..230df1c10 --- /dev/null +++ b/src/startup/mldr/loader.h @@ -0,0 +1,31 @@ +#ifndef _MLDR_LOADER_H_ +#define _MLDR_LOADER_H_ + +#include +#include +#include + +struct load_results { + unsigned long mh; + unsigned long entry_point; + unsigned long stack_size; + unsigned long dyld_all_image_location; + unsigned long dyld_all_image_size; + uint8_t uuid[16]; + + unsigned long vm_addr_max; + bool _32on64; + unsigned long base; + uint32_t bprefs[4]; + char* root_path; + unsigned long stack_top; + char* socket_path; + int kernfd; + + size_t argc; + size_t envc; + char** argv; + char** envp; +}; + +#endif // _MLDR_LOADER_H_ diff --git a/src/startup/mldr/mldr.c b/src/startup/mldr/mldr.c new file mode 100644 index 000000000..15dc89e3e --- /dev/null +++ b/src/startup/mldr/mldr.c @@ -0,0 +1,526 @@ +/* +This file is part of Darling. + +Copyright (C) 2017 Lubos Dolezel + +Darling is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Darling is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Darling. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "commpage.h" +#include "loader.h" +#include +#include +#include +#include +#include + +#ifndef PAGE_SIZE +# define PAGE_SIZE 4096 +#endif +#define PAGE_ALIGN(x) (x & ~(PAGE_SIZE-1)) + +static const char* dyld_path = INSTALL_PREFIX "/libexec/usr/lib/dyld"; + +struct sockaddr_un __dserver_socket_address_data = { + .sun_family = AF_UNIX, + .sun_path = "\0", +}; + +int __dserver_main_thread_socket_fd = -1; + +// The idea of mldr is to load dyld_path into memory and set up the stack +// as described in dyldStartup.S. +// After that, we pass control over to dyld. +// +// Additionally, mldr providers access to native platforms libdl.so APIs (ELF loader). + +#ifdef __x86_64__ +static void load64(int fd, bool expect_dylinker, struct load_results* lr); +static void reexec32(char** argv); +#endif +static void load32(int fd, bool expect_dylinker, struct load_results* lr); +static void load_fat(int fd, cpu_type_t cpu, bool expect_dylinker, char** argv, struct load_results* lr); +static void load(const char* path, cpu_type_t cpu, bool expect_dylinker, char** argv, struct load_results* lr); +static int native_prot(int prot); +static void setup_space(struct load_results* lr, bool is_64_bit); +static void process_special_env(struct load_results* lr); +static void start_thread(struct load_results* lr); +#ifdef __x86_64__ +static void setup_stack64(const char* filepath, struct load_results* lr); +#endif +static void setup_stack32(const char* filepath, struct load_results* lr); + +// UUID of the main executable +uint8_t exe_uuid[16]; + +// Data for TASK_DYLD_INFO +uintptr_t dyld_all_image_location; +size_t dyld_all_image_size; + +static uint32_t stack_size = 0; + +int main(int argc, char** argv, char** envp) +{ + void** sp; + int pushCount = 0; + char *filename, *p = NULL; + struct load_results lr = {0}; + + lr.kernfd = -1; + lr.argc = argc; + lr.argv = argv; + + while (envp[lr.envc] != NULL) { + ++lr.envc; + } + lr.envp = envp; + + // sys_execve() passes the original file path appended to the mldr path in argv[0]. + if (argc > 0) + p = strchr(argv[0], '!'); + + if (argc <= 1) + { + if (p == NULL) { + fprintf(stderr, "mldr is part of Darling. It is not to be executed directly.\n"); + return 1; + } + else + { + fprintf(stderr, "mldr: warning: Executing with no argv[0]. Continuing anyway, but this is probably a bug.\n"); + } + } + + if (p != NULL) + { + filename = (char*) __builtin_alloca(strlen(argv[0])+1); + strcpy(filename, p + 1); + } + else + { + filename = (char*) __builtin_alloca(strlen(argv[1])+1); + strcpy(filename, argv[1]); + } + +#ifdef __i386__ + load(filename, CPU_TYPE_X86, false, argv, &lr); // accept i386 only +#else + load(filename, 0, false, argv, &lr); +#endif + + // this was previously necessary when we were loading the binary from the LKM + // (presumably because the break was detected incorrectly) + // but this shouldn't be necessary for loading Mach-O's from userspace (the heap space should already be set up properly). + // see https://github.com/darlinghq/darling/issues/469 for the issue this originally fixed in the LKM +#if 0 + if (prctl(PR_SET_MM, PR_SET_MM_BRK, PAGE_ALIGN(lr.vm_addr_max), 0, 0) < 0) { + fprintf(stderr, "Failed to set BRK value\n"); + return 1; + } + + if (prctl(PR_SET_MM, PR_SET_MM_START_BRK, PAGE_ALIGN(lr.vm_addr_max), 0, 0) < 0) { + fprintf(stderr, "Failed to set BRK start\n"); + return 1; + } +#endif + + if (prctl(PR_SET_MM, PR_SET_MM_START_STACK, lr.stack_top, 0, 0) < 0) { + fprintf(stderr, "Failed to set stack start\n"); + return 1; + } + + // adjust argv (remove mldr's argv[0]) + --lr.argc; + for (size_t i = 0; i < lr.argc; ++i) { + lr.argv[i] = lr.argv[i + 1]; + } + lr.argv[lr.argc] = NULL; + + if (lr._32on64) + setup_stack32(filename, &lr); + else +#ifdef __x86_64__ + setup_stack64(filename, &lr); +#elif __aarch64__ + #error TODO: aarch64 +#else + abort(); +#endif + + // TODO: tell darlingserver about our dyld info + + start_thread(&lr); + + __builtin_unreachable(); +} + +void load(const char* path, cpu_type_t forced_arch, bool expect_dylinker, char** argv, struct load_results* lr) +{ + int fd; + uint32_t magic; + + fd = open(path, O_RDONLY); + if (fd == -1) + { + fprintf(stderr, "Cannot open %s: %s\n", path, strerror(errno)); + exit(1); + } + + // We need to read argv[1] and detect whether it's a 32 or 64-bit application. + // Then load the appropriate version of dyld from the fat file. + // In case the to-be-executed executable contains both, we prefer the 64-bit version, + // unless a special property has been passed to sys_posix_spawn() to force the 32-bit + // version. See posix_spawnattr_setbinpref_np(). + + if (read(fd, &magic, sizeof(magic)) != sizeof(magic)) + { + fprintf(stderr, "Cannot read the file header of %s.\n", path); + exit(1); + } + + if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) + { +#ifdef __x86_64__ + lseek(fd, 0, SEEK_SET); + load64(fd, expect_dylinker, lr); +#else + abort(); +#endif + } + else if (magic == MH_MAGIC || magic == MH_CIGAM) + { +#if !__x86_64__ + lseek(fd, 0, SEEK_SET); + load32(fd, expect_dylinker, lr); +#else + // Re-run self as mldr32 + reexec32(argv); +#endif + } + else if (magic == FAT_MAGIC || magic == FAT_CIGAM) + { + lseek(fd, 0, SEEK_SET); + load_fat(fd, forced_arch, expect_dylinker, argv, lr); + } + else + { + fprintf(stderr, "Unknown file format: %s.\n", path); + exit(1); + } + + close(fd); +} + +static void load_fat(int fd, cpu_type_t forced_arch, bool expect_dylinker, char** argv, struct load_results* lr) { + struct fat_header fhdr; + struct fat_arch best_arch = {0}; + int bpref_index = -1; + + best_arch.cputype = CPU_TYPE_ANY; + + if (read(fd, &fhdr, sizeof(fhdr)) != sizeof(fhdr)) + { + fprintf(stderr, "Cannot read fat file header.\n"); + exit(1); + } + + const bool swap = fhdr.magic == FAT_CIGAM; + +#define SWAP32(x) x = __bswap_32(x) + + if (swap) + SWAP32(fhdr.nfat_arch); + + uint32_t i; + for (i = 0; i < fhdr.nfat_arch; i++) + { + struct fat_arch arch; + + if (read(fd, &arch, sizeof(arch)) != sizeof(arch)) + { + fprintf(stderr, "Cannot read fat_arch header.\n"); + exit(1); + } + + if (swap) + { + SWAP32(arch.cputype); + SWAP32(arch.cpusubtype); + SWAP32(arch.offset); + SWAP32(arch.size); + SWAP32(arch.align); + } + + if (!forced_arch) + { + int j; + for (j = 0; j < 4; j++) + { + if (lr->bprefs[j] && arch.cputype == lr->bprefs[j]) + { + if (bpref_index == -1 || bpref_index > j) + { + best_arch = arch; + bpref_index = j; + break; + } + } + } + + if (bpref_index == -1) + { +#if defined(__x86_64__) + if (arch.cputype == CPU_TYPE_X86_64) + best_arch = arch; + else if (best_arch.cputype == CPU_TYPE_ANY && arch.cputype == CPU_TYPE_X86) + best_arch = arch; +#elif defined(__i386__) + if (arch.cputype == CPU_TYPE_X86) + best_arch = arch; +#elif defined (__aarch64__) + #error TODO: arm +#else + #error Unsupported CPU architecture +#endif + } + } + else + { + if (arch.cputype == forced_arch) + best_arch = arch; + } + } + + if (best_arch.cputype == CPU_TYPE_ANY) + { + fprintf(stderr, "No supported architecture found in fat binary.\n"); + exit(1); + } + + if (lseek(fd, best_arch.offset, SEEK_SET) == -1) + { + fprintf(stderr, "Cannot seek to selected arch in fat binary.\n"); + exit(1); + } + + if (best_arch.cputype & CPU_ARCH_ABI64) { +#ifdef __x86_64__ + load64(fd, expect_dylinker, lr); +#elif __aarch64__ + #error TODO: aarch64 +#else + abort(); +#endif + } else { +#if !__x86_64__ + load32(fd, expect_dylinker, lr); +#else + // Re-run self as mldr32 + reexec32(argv); +#endif + } +}; + +#ifdef __x86_64__ +#define GEN_64BIT +#include "loader.c" +#include "stack.c" +#undef GEN_64BIT +#endif + +#define GEN_32BIT +#include "loader.c" +#include "stack.c" +#undef GEN_32BIT + +int native_prot(int prot) +{ + int protOut = 0; + + if (prot & VM_PROT_READ) + protOut |= PROT_READ; + if (prot & VM_PROT_WRITE) + protOut |= PROT_WRITE; + if (prot & VM_PROT_EXECUTE) + protOut |= PROT_EXEC; + + return protOut; +} + +static void reexec32(char** argv) +{ + char selfpath[1024]; + ssize_t len; + + len = readlink("/proc/self/exe", selfpath, sizeof(selfpath)-3); + if (len == -1) + { + perror("Cannot readlink /proc/self/exe"); + abort(); + } + + selfpath[len] = '\0'; + strcat(selfpath, "32"); + + execv(selfpath, argv); + + perror("Cannot re-execute as 32-bit process"); + abort(); +} + +// Given that there's no proper way of passing special parameters to the binary loader +// via execve(), we must do this via env variables +static void process_special_env(struct load_results* lr) { + const char* str; + static char root_path[4096]; + + lr->bprefs[0] = lr->bprefs[1] = lr->bprefs[2] = lr->bprefs[3] = 0; + str = getenv("__mldr_bprefs"); + + if (str != NULL) { + sscanf(str, "%x,%x,%x,%x", &lr->bprefs[0], &lr->bprefs[1], &lr->bprefs[2], &lr->bprefs[3]); + unsetenv("__mldr_bprefs"); + } + + str = getenv("__mldr_sockpath"); + + if (str != NULL) { + if (strlen(str) > sizeof(__dserver_socket_address_data.sun_path) - 1) { + fprintf(stderr, "darlingserver socket path is too long\n"); + exit(1); + } + strncpy(__dserver_socket_address_data.sun_path, str, sizeof(__dserver_socket_address_data.sun_path) - 1); + __dserver_socket_address_data.sun_path[sizeof(__dserver_socket_address_data.sun_path) - 1] = '\0'; + unsetenv("__mldr_sockpath"); + + lr->socket_path = __dserver_socket_address_data.sun_path; + } + + str = getenv("DYLD_ROOT_PATH"); + + if (str != NULL && lr->root_path == NULL) { + strncpy(root_path, str, sizeof(root_path) - 1); + root_path[sizeof(root_path) - 1] = '\0'; + lr->root_path = root_path; + } +}; + +static void setup_space(struct load_results* lr, bool is_64_bit) { + commpage_setup(is_64_bit); + + // Using the default stack top would cause the stack to be placed just above the commpage + // and would collide with it eventually. + // Instead, we manually allocate a new stack below the commpage. +#if __x86_64__ + lr->stack_top = commpage_address(true); +#elif __i386__ + lr->stack_top = commpage_address(false); +#else + #error Unsupported architecture +#endif + + struct rlimit limit; + getrlimit(RLIMIT_STACK, &limit); + // allocate a few pages 16 pages if it's less than the limit; otherwise, allocate the limit + unsigned long size = PAGE_SIZE * 16; + if (limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < size) { + size = limit.rlim_cur; + } + + if (mmap((void*)lr->stack_top, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_GROWSDOWN, -1, 0) == MAP_FAILED) { + fprintf(stderr, "Failed to allocate stack of %lu bytes: %d (%s)\n", size, errno, strerror(errno)); + exit(1); + } + + process_special_env(lr); + + lr->kernfd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (lr->kernfd < 0) { + fprintf(stderr, "Failed to create socket\n"); + exit(1); + } + + sa_family_t family = AF_UNIX; + if (bind(lr->kernfd, (const struct sockaddr*)&family, sizeof(family)) < 0) { + fprintf(stderr, "Failed to autobind socket\n"); + exit(1); + } + + __dserver_main_thread_socket_fd = lr->kernfd; + + if (dserver_rpc_checkin() < 0) { + fprintf(stderr, "Failed to checkin with darlingserver\n"); + exit(1); + } + + static char vchroot_buffer[4096]; + size_t vchroot_path_length = 0; + + if (dserver_rpc_vchroot_path(vchroot_buffer, sizeof(vchroot_buffer), &vchroot_path_length) < 0) { + fprintf(stderr, "Failed to retrieve vchroot path from darlingserver\n"); + exit(1); + } + + if (vchroot_path_length > 0) { + lr->root_path = vchroot_buffer; + } +}; + +static void start_thread(struct load_results* lr) { +#ifdef __x86_64__ + __asm__ volatile( + "mov %1, %%rsp\n" + "jmpq *%0" + :: + "m"(lr->entry_point), + "r"(lr->stack_top) + : + ); +#elif defined(__i386__) + __asm__ volatile( + "mov %1, %%esp\n" + "jmp *%0" + :: + "m"(lr->entry_point), + "r"(lr->stack_top) + : + ); +#elif defined(__arm__) + __asm__ volatile( + "mov sp, %1\n" + "bx %0" + :: + "r"(lr->entry_point), + "r"(lr->stack_top) + : + ); +#else +# error Unsupported platform! +#endif +}; diff --git a/src/startup/mldr/resources/dserver-rpc-defs.h b/src/startup/mldr/resources/dserver-rpc-defs.h new file mode 100644 index 000000000..d3c599de4 --- /dev/null +++ b/src/startup/mldr/resources/dserver-rpc-defs.h @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include +#include + +#define dserver_rpc_hooks_msghdr_t struct msghdr +#define dserver_rpc_hooks_iovec_t struct iovec +#define dserver_rpc_hooks_cmsghdr_t struct cmsghdr +#define DSERVER_RPC_HOOKS_CMSG_SPACE CMSG_SPACE +#define DSERVER_RPC_HOOKS_CMSG_FIRSTHDR CMSG_FIRSTHDR +#define DSERVER_RPC_HOOKS_SOL_SOCKET SOL_SOCKET +#define DSERVER_RPC_HOOKS_SCM_RIGHTS SCM_RIGHTS +#define DSERVER_RPC_HOOKS_CMSG_LEN CMSG_LEN +#define DSERVER_RPC_HOOKS_CMSG_DATA CMSG_DATA +#define DSERVER_RPC_HOOKS_ATTRIBUTE static + +#define dserver_rpc_hooks_get_pid getpid + +#define dserver_rpc_hooks_get_tid() ((pid_t)syscall(SYS_gettid)) + +extern struct sockaddr_un __dserver_socket_address_data; + +#define dserver_rpc_hooks_get_server_address() ((void*)&__dserver_socket_address_data) + +#define dserver_rpc_hooks_get_server_address_length() sizeof(__dserver_socket_address_data) + +#define dserver_rpc_hooks_memcpy memcpy + +static long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hooks_msghdr_t* message) { + ssize_t ret = sendmsg(socket, message, 0); + if (ret < 0) { + return -errno; + } + return ret; +}; + +static long int dserver_rpc_hooks_receive_message(int socket, dserver_rpc_hooks_msghdr_t* out_message) { + ssize_t ret = recvmsg(socket, out_message, 0); + if (ret < 0) { + return -errno; + } + return ret; +}; + +#define dserver_rpc_hooks_get_bad_message_status() EBADMSG + +#define dserver_rpc_hooks_get_communication_error_status() ECOMM + +#define dserver_rpc_hooks_get_broken_pipe_status() EPIPE + +#define dserver_rpc_hooks_close_fd close + +extern int __dserver_main_thread_socket_fd; + +#define dserver_rpc_hooks_get_socket() __dserver_main_thread_socket_fd diff --git a/src/startup/mldr/stack.c b/src/startup/mldr/stack.c new file mode 100644 index 000000000..68b3bda25 --- /dev/null +++ b/src/startup/mldr/stack.c @@ -0,0 +1,260 @@ +/* + * This file is part of Darling. + * Copyright (C) 2021 Darling developers + * + * Originally part of the Darling Mach Linux Kernel Module + * Copyright (C) 2017 Lubos Dolezel + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include "loader.h" +#include +#include "elfcalls/elfcalls.h" +#include + +#if defined(GEN_64BIT) +#define FUNCTION_NAME setup_stack64 +#define user_long_t unsigned long +#elif defined(GEN_32BIT) +#define FUNCTION_NAME setup_stack32 +#define user_long_t unsigned int +#else +#error See above +#endif + +#define __user + +#define EXECUTABLE_PATH "executable_path=" + +#define __put_user(value, pointer) ({ \ + __typeof__(value) _tmpval = (value); \ + memcpy((pointer), &_tmpval, sizeof(_tmpval)); \ + 0; \ + }) + +void elfcalls_make(struct elf_calls* calls); + +static struct elf_calls _elfcalls; + +void FUNCTION_NAME(const char* filepath, struct load_results* lr) +{ + int err = 0; + // unsigned char rand_bytes[16]; + char *executable_path; + static char executable_buf[4096]; + user_long_t __user* argv; + user_long_t __user* envp; + user_long_t __user* applep; + user_long_t __user* sp; + char __user* exepath_user; + size_t exepath_len; + char __user* kernfd_user; + char kernfd[12]; + char __user* elfcalls_user; + char elfcalls[27]; + char __user* applep_contents[4]; + char* vchroot_path = NULL; + +#define user_long_count(_val) (((_val) + (sizeof(user_long_t) - 1)) / sizeof(user_long_t)) + + elfcalls_make(&_elfcalls); + + // Produce executable_path=... for applep + executable_buf[sizeof(executable_buf) - 1] = '\0'; + strncpy(executable_buf, filepath, 4096); + if (executable_buf[sizeof(executable_buf) - 1] != '\0') + { + fprintf(stderr, "File path was too big\n"); + exit(1); + } + + executable_path = executable_buf; + + // TODO: ask darlingserver for our vchroot path + + if (vchroot_path != NULL) + { + const size_t vchroot_len = strlen(vchroot_path); + exepath_len = strlen(executable_path); + + if (strncmp(executable_path, vchroot_path, vchroot_len) == 0) + { + memmove(executable_buf, executable_path + vchroot_len, exepath_len - vchroot_len + 1); + } + else + { + memcpy(executable_buf, SYSTEM_ROOT, sizeof(SYSTEM_ROOT) - 1); + memmove(executable_buf + sizeof(SYSTEM_ROOT) - 1, executable_path, exepath_len + 1); + } + executable_path = executable_buf; + } + + // printk(KERN_NOTICE "Stack top: %p\n", bprm->p); + exepath_len = strlen(executable_path); + sp = (user_long_t*) (lr->stack_top & ~(sizeof(user_long_t)-1)); + + // 1 pointer for the mach header + // 1 user_long_t for the argument count + // `argc`-count pointers for arguments (+1 for NULL) + // `envc`-count pointers for env vars (+1 for NULL) + // `sizeof(applep_contents) / sizeof(*applep_contents)`-count pointers for applep arguments (already includes NULL) + // space for exepath, kernfd, and elfcalls + sp -= 1 + 1 + (lr->argc + 1) + (lr->envc + 1) + (sizeof(applep_contents) / sizeof(*applep_contents)) + user_long_count(exepath_len + sizeof(EXECUTABLE_PATH) + sizeof(kernfd) + sizeof(elfcalls)); + + exepath_user = (char __user*) lr->stack_top - exepath_len - sizeof(EXECUTABLE_PATH); + memcpy(exepath_user, EXECUTABLE_PATH, sizeof(EXECUTABLE_PATH)-1); + memcpy(exepath_user + sizeof(EXECUTABLE_PATH)-1, executable_path, exepath_len + 1); + + snprintf(kernfd, sizeof(kernfd), "kernfd=%d", lr->kernfd); + kernfd_user = exepath_user - sizeof(kernfd); + memcpy(kernfd_user, kernfd, sizeof(kernfd)); + +#if defined(GEN_64BIT) + #define POINTER_FORMAT "%lx" +#elif defined(GEN_32BIT) + #define POINTER_FORMAT "%x" +#endif + + snprintf(elfcalls, sizeof(elfcalls), "elf_calls=" POINTER_FORMAT, (unsigned long)(uintptr_t)&_elfcalls); + elfcalls_user = kernfd_user - sizeof(elfcalls); + memcpy(elfcalls_user, elfcalls, sizeof(elfcalls)); + + applep_contents[0] = exepath_user; + applep_contents[1] = kernfd_user; + applep_contents[2] = elfcalls_user; + applep_contents[3] = NULL; + + lr->stack_top = (unsigned long) sp; + + // XXX: skip this for static executables, but we don't support them anyway... + if (__put_user((user_long_t) lr->mh, sp++)) + { + fprintf(stderr, "Failed to copy mach header address to stack\n"); + exit(1); + } + if (__put_user((user_long_t) lr->argc, sp++)) + { + fprintf(stderr, "Failed to copy argument count to stack\n"); + exit(1); + } + + // Fill in argv pointers + argv = sp; + if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, argv, 0, 0) < 0) { + // maybe arg_end was behind arg_start; try moving it first + if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, argv, 0, 0) < 0) { + fprintf(stderr, "Failed to set arg end\n"); + exit(1); + } + if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, argv, 0, 0) < 0) { + fprintf(stderr, "Failed to set arg start\n"); + exit(1); + } + } + for (int i = 0; i < lr->argc; ++i) + { + if (!lr->argv[i]) { + lr->argc = i; + break; + } + if (__put_user((user_long_t) lr->argv[i], argv++)) + { + fprintf(stderr, "Failed to copy an argument pointer to stack\n"); + exit(1); + } + } + if (__put_user((user_long_t) 0, argv++)) + { + fprintf(stderr, "Failed to null-terminate the argument pointer array\n"); + exit(1); + } + if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, argv, 0, 0) < 0) { + fprintf(stderr, "Failed to set arg end\n"); + exit(1); + } + + // Fill in envp pointers + envp = argv; + if (prctl(PR_SET_MM, PR_SET_MM_ENV_START, envp, 0, 0) < 0) { + // maybe env_end was behind env_start; try moving it first + if (prctl(PR_SET_MM, PR_SET_MM_ENV_END, envp, 0, 0) < 0) { + fprintf(stderr, "Failed to set env end\n"); + exit(1); + } + if (prctl(PR_SET_MM, PR_SET_MM_ENV_START, envp, 0, 0) < 0) { + fprintf(stderr, "Failed to set env start\n"); + exit(1); + } + } + for (int i = 0; i < lr->envc; ++i) + { + if (!lr->envp[i]) { + lr->envc = i; + break; + } + + size_t len = strlen((void __user*) lr->envp[i]) + 1; + + // Don't pass these special env vars down to userland + #define SKIP_VAR(_name) \ + if (len > sizeof(_name) - 1 && strncmp(lr->envp[i], _name, sizeof(_name) - 1) == 0) { \ + continue; \ + } + + SKIP_VAR("__mldr_bprefs="); + SKIP_VAR("__mldr_sockpath="); + + if (__put_user((user_long_t) lr->envp[i], envp++)) + { + fprintf(stderr, "Failed to copy an environment variable pointer to stack\n"); + exit(1); + } + } + if (__put_user((user_long_t) 0, envp++)) + { + fprintf(stderr, "Failed to null-terminate the environment variable pointer array\n"); + exit(1); + } + if (prctl(PR_SET_MM, PR_SET_MM_ENV_END, envp, 0, 0) < 0) { + fprintf(stderr, "Failed to set env end\n"); + exit(1); + } + + applep = envp; // envp is now at the end of env pointers + + for (int i = 0; i < sizeof(applep_contents)/sizeof(applep_contents[0]); i++) + { + if (__put_user((user_long_t)(unsigned long) applep_contents[i], applep++)) + { + fprintf(stderr, "Failed to copy an applep value to stack\n"); + exit(1); + } + } + + // get_random_bytes(rand_bytes, sizeof(rand_bytes)); + + // TODO: produce stack_guard, e.g. stack_guard=0xcdd5c48c061b00fd (must contain 00 somewhere!) + // TODO: produce malloc_entropy, e.g. malloc_entropy=0x9536cc569d9595cf,0x831942e402da316b + // TODO: produce main_stack? +} + +#undef FUNCTION_NAME +#undef user_long_t