/*! * \brief Get first line from file * * The trailing newline (if exists) will be stripped from the result. * * \param path File to read * * \return Nothing on success or the error code on failure. Returns * FileError::UnexpectedEof if the file is empty. */ oc::result<std::string> file_first_line(const std::string &path) { ScopedFILE fp(fopen(path.c_str(), "rbe"), fclose); if (!fp) { return ec_from_errno(); } char *line = nullptr; size_t len = 0; ssize_t read; auto free_line = finally([&] { free(line); }); if ((read = getline(&line, &len, fp.get())) < 0) { if (ferror(fp.get())) { return ec_from_errno(); } else { return FileError::UnexpectedEof; } } if (read > 0 && line[read - 1] == '\n') { line[read - 1] = '\0'; --read; } return line; }
// Based on procattr.c from libselinux (public domain) static oc::result<int> open_attr(pid_t pid, SELinuxAttr attr, int flags) { const char *attr_name; switch (attr) { case SELinuxAttr::Current: attr_name = "current"; break; case SELinuxAttr::Exec: attr_name = "exec"; break; case SELinuxAttr::FsCreate: attr_name = "fscreate"; break; case SELinuxAttr::KeyCreate: attr_name = "keycreate"; break; case SELinuxAttr::Prev: attr_name = "prev"; break; case SELinuxAttr::SockCreate: attr_name = "sockcreate"; break; default: return std::errc::invalid_argument; } std::string path; if (pid > 0) { path = format("/proc/%d/attr/%s", pid, attr_name); } else if (pid == 0) { path = format("/proc/thread-self/attr/%s", attr_name); int fd = open(path.c_str(), flags | O_CLOEXEC); if (fd < 0 && errno != ENOENT) { return ec_from_errno(); } else if (fd >= 0) { return fd; } path = format("/proc/self/task/%d/attr/%s", gettid(), attr_name); } else { return std::errc::invalid_argument; } int fd = open(path.c_str(), flags | O_CLOEXEC); if (fd < 0) { return ec_from_errno(); } return fd; }
oc::result<void> selinux_set_process_attr(pid_t pid, SELinuxAttr attr, const std::string &context) { OUTCOME_TRY(fd, open_attr(pid, attr, O_WRONLY | O_CLOEXEC)); auto close_fd = finally([&] { close(fd); }); ssize_t n; if (context.empty()) { // Clear context in the attr file if context is empty do { n = write(fd, nullptr, 0); } while (n < 0 && errno == EINTR); } else { // Otherwise, write the new context. The written string must be // NULL-terminated do { n = write(fd, context.c_str(), context.size() + 1); } while (n < 0 && errno == EINTR); } if (n < 0) { return ec_from_errno(); } return oc::success(); }
oc::result<void> selinux_set_enforcing(bool value) { int fd = open(SELINUX_ENFORCE_FILE, O_RDWR | O_CLOEXEC); if (fd < 0) { return ec_from_errno(); } auto close_fd = finally([&] { close(fd); }); if (write(fd, value ? "1" : "0", 1) < 0) { return ec_from_errno(); } return oc::success(); }
oc::result<void> selinux_fset_context(int fd, const std::string &context) { if (fsetxattr(fd, SELINUX_XATTR, context.c_str(), context.size() + 1, 0) < 0) { return ec_from_errno(); } return oc::success(); }
oc::result<void> selinux_lset_context(const std::string &path, const std::string &context) { if (lsetxattr(path.c_str(), SELINUX_XATTR, context.c_str(), context.size() + 1, 0) < 0) { return ec_from_errno(); } return oc::success(); }
oc::result<std::string> file_read_all(const std::string &path) { ScopedFILE fp(fopen(path.c_str(), "rbe"), fclose); if (!fp) { return ec_from_errno(); } std::string data; // Reduce allocations if possible if (fseeko(fp.get(), 0, SEEK_END) == 0) { auto size = ftello(fp.get()); if (size < 0) { return ec_from_errno(); } else if (std::make_unsigned_t<decltype(size)>(size) > SIZE_MAX) { return std::errc::result_out_of_range; } data.reserve(static_cast<size_t>(size)); } if (fseeko(fp.get(), 0, SEEK_SET) < 0) { return ec_from_errno(); } char buf[8192]; while (true) { auto n = fread(buf, 1, sizeof(buf), fp.get()); data.insert(data.end(), buf, buf + n); if (n < sizeof(buf)) { if (ferror(fp.get())) { return ec_from_errno(); } else { break; } } } return std::move(data); }
oc::result<bool> file_find_one_of(const std::string &path, const std::vector<std::string> &items) { struct stat sb; void *map = MAP_FAILED; int fd = -1; if ((fd = open(path.c_str(), O_RDONLY | O_CLOEXEC)) < 0) { return ec_from_errno(); } auto close_fd = finally([&] { close(fd); }); if (fstat(fd, &sb) < 0) { return ec_from_errno(); } map = mmap(nullptr, static_cast<size_t>(sb.st_size), PROT_READ, MAP_PRIVATE, fd, 0); if (map == MAP_FAILED) { return ec_from_errno(); } auto unmap_map = finally([&] { munmap(map, static_cast<size_t>(sb.st_size)); }); for (auto const &item : items) { if (memmem(map, static_cast<size_t>(sb.st_size), item.data(), item.size())) { return true; } } return false; }
oc::result<uint64_t> get_blockdev_size(const std::string &path) { int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC); if (fd < 0) { return ec_from_errno(); } auto close_fd = finally([&]{ close(fd); }); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" uint64_t size; if (ioctl(fd, BLKGETSIZE64, &size) < 0) { return ec_from_errno(); } #pragma GCC diagnostic pop return size; }
/*! * \brief Write data to a file * * \param path File to write * \param data Pointer to data to write * \param size Size of \a data * * \return Nothing on success or the error code on failure. Returns * FileError::UnexpectedEof if EOF is reached before the write * completes. */ oc::result<void> file_write_data(const std::string &path, const void *data, size_t size) { ScopedFILE fp(fopen(path.c_str(), "wbe"), fclose); if (!fp) { return ec_from_errno(); } size_t n = std::fwrite(data, 1, size, fp.get()); if (n != size) { if (ferror(fp.get())) { return ec_from_errno(); } else { return FileError::UnexpectedEof; } } if (fclose(fp.release()) != 0) { return ec_from_errno(); } return oc::success(); }
oc::result<bool> selinux_get_enforcing() { int fd = open(SELINUX_ENFORCE_FILE, O_RDONLY | O_CLOEXEC); if (fd < 0) { return ec_from_errno(); } auto close_fd = finally([&] { close(fd); }); char buf[20] = {}; if (read(fd, buf, sizeof(buf) - 1) < 0) { return ec_from_errno(); } int enforce = 0; if (sscanf(buf, "%d", &enforce) != 1) { return std::errc::invalid_argument; } return oc::success(enforce); }
oc::result<std::string> selinux_fget_context(int fd) { std::string value; ssize_t size = fgetxattr(fd, SELINUX_XATTR, nullptr, 0); if (size < 0) { return ec_from_errno(); } value.resize(static_cast<size_t>(size)); size = fgetxattr(fd, SELINUX_XATTR, value.data(), static_cast<size_t>(size)); if (size < 0) { return ec_from_errno(); } if (!value.empty() && value.back() == '\0') { value.pop_back(); } return std::move(value); }
/*! * \brief Format a string using a `va_list` * * \note This uses the `*printf()` family of functions in the system's C * library. The format string may not be understood the same way by every * platform. * * \note The value of `errno` and `GetLastError()` (on Win32) are always * preserved. * * \param fmt Format string * \param ap Format arguments as `va_list` * * \return Resulting formatted string or error. */ oc::result<std::string> format_v_safe(const char *fmt, va_list ap) { static_assert(INT_MAX <= SIZE_MAX, "INT_MAX > SIZE_MAX"); ErrorRestorer restorer; int ret; va_list copy; std::string buf; va_copy(copy, ap); ret = vsnprintf(nullptr, 0, fmt, copy); va_end(copy); if (ret < 0) { return ec_from_errno(); } else if (static_cast<size_t>(ret) == SIZE_MAX) { return std::make_error_code(std::errc::value_too_large); } // C++11 guarantees that the memory is contiguous, but does not guarantee // that the internal buffer is NULL-terminated, so we'll make room for '\0' // and then get rid of it. buf.resize(static_cast<size_t>(ret) + 1); va_copy(copy, ap); // NOTE: Change `&buf[0]` to `buf.data()` once we target C++17. ret = vsnprintf(&buf[0], buf.size(), fmt, copy); va_end(copy); if (ret < 0) { return ec_from_errno(); } buf.resize(static_cast<size_t>(ret)); return std::move(buf); }
oc::result<void> selinux_lset_context_recursive(const std::string &path, const std::string &context) { RecursiveSetContext rsc(path, context, false); if (!rsc.run()) { auto ret = rsc.result(); if (ret) { return ec_from_errno(); } else { return ret.as_failure(); } } return rsc.result(); }
oc::result<void> socket_receive_fds(int fd, std::vector<int> &fds) { char dummy; struct iovec iov; iov.iov_base = &dummy; iov.iov_len = 1; std::vector<char> control( sizeof(struct cmsghdr) + sizeof(int) * fds.size()); struct msghdr msg; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = control.data(); msg.msg_controllen = control.size(); struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = msg.msg_controllen; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; if (recvmsg(fd, &msg, 0) < 0) { return ec_from_errno(); } int *data = reinterpret_cast<int *>(CMSG_DATA(cmsg)); std::size_t n_fds = (cmsg->cmsg_len - sizeof(struct cmsghdr)) / sizeof(int); if (n_fds != fds.size()) { // Did not receive correct amount of file descriptors return std::errc::bad_message; } for (std::size_t i = 0; i < n_fds; ++i) { fds[i] = data[i]; } return oc::success(); }
oc::result<void> socket_send_fds(int fd, const std::vector<int> &fds) { if (fds.empty()) { return std::errc::invalid_argument; } char dummy = '!'; struct iovec iov; iov.iov_base = &dummy; iov.iov_len = 1; std::vector<char> control( sizeof(struct cmsghdr) + sizeof(int) * fds.size()); struct msghdr msg; msg.msg_name = nullptr; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = control.data(); msg.msg_controllen = control.size(); struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = msg.msg_controllen; cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; int *data = reinterpret_cast<int *>(CMSG_DATA(cmsg)); for (std::size_t i = 0; i < fds.size(); ++i) { data[i] = fds[i]; } if (sendmsg(fd, &msg, 0) < 0) { return ec_from_errno(); } return oc::success(); }
oc::result<std::string> selinux_get_process_attr(pid_t pid, SELinuxAttr attr) { OUTCOME_TRY(fd, open_attr(pid, attr, O_RDONLY)); auto close_fd = finally([&]{ close(fd); }); std::vector<char> buf(static_cast<size_t>(sysconf(_SC_PAGE_SIZE))); ssize_t n; do { n = read(fd, buf.data(), buf.size() - 1); } while (n < 0 && errno == EINTR); if (n < 0) { return ec_from_errno(); } // buf is guaranteed to be NULL-terminated return buf.data(); }
/*! * \brief Insert byte sequence into byte sequence * * Insert (\p data, \p data_pos) into (\p *mem, \p *mem_size). It the function * succeeds, \p *mem will be passed to `free()` and \p *mem and \p *mem_size * will be updated to point to the newly allocated block of memory and its size. * If the function fails, \p *mem will be left unchanged. * * \param[in,out] mem Pointer to byte sequence to modify * \param[in,out] mem_size Pointer to size of bytes sequence to modify * \param[in] pos Position in which to insert new byte sequence * (0 \<= \p pos \<= \p *mem_size) * \param[in] data New byte sequence to insert * \param[in] data_size Size of new byte sequence to insert * * \return Nothing if successful. Otherwise, the error code. */ oc::result<void> mem_insert(void **mem, size_t *mem_size, size_t pos, const void *data, size_t data_size) { void *buf; size_t buf_size; if (pos > *mem_size) { return std::make_error_code(std::errc::invalid_argument); } else if (*mem_size >= SIZE_MAX - data_size) { return std::make_error_code(std::errc::value_too_large); } buf_size = *mem_size + data_size; buf = static_cast<char *>(malloc(buf_size)); if (!buf) { return ec_from_errno(); } void *target_ptr = buf; // Copy data left of the insertion point target_ptr = _mb_mempcpy(target_ptr, *mem, pos); // Copy new data target_ptr = _mb_mempcpy(target_ptr, data, data_size); // Copy data right of the insertion point target_ptr = _mb_mempcpy(target_ptr, static_cast<char*>(*mem) + pos, *mem_size - pos); free(*mem); *mem = buf; *mem_size = buf_size; return oc::success(); }
/*! * \brief Replace byte sequence in byte sequence * * Replace (\p from, \p from_size) with (\p to, \p to_size) in (\p *mem, * \p *mem_size), up to \p n times if \p n \> 0. If the function succeeds, * \p *mem will be passed to `free()` and \p *mem and \p *mem_size will be * updated to point to the newly allocated block of memory and its size. If * \p n_replaced is not NULL, the number of replacements done will be stored at * the value pointed by \p n_replaced. If the function fails, \p *mem will be * left unchanged. * * \param[in,out] mem Pointer to byte sequence to modify * \param[in,out] mem_size Pointer to size of bytes sequence to modify * \param[in] from Byte sequence to replace * \param[in] from_size Size of byte sequence to replace * \param[in] to Replacement byte sequence * \param[in] to_size Size of replacement byte sequence * \param[in] n Number of replacements to attempt (0 to disable limit) * \param[out] n_replaced Pointer to store number of replacements made * * \return Nothing if successful. Otherwise, the error code. */ oc::result<void> mem_replace(void **mem, size_t *mem_size, const void *from, size_t from_size, const void *to, size_t to_size, size_t n, size_t *n_replaced) { char *buf = nullptr; size_t buf_size = 0; void *target_ptr; auto base_ptr = static_cast<char *>(*mem); auto ptr = static_cast<char *>(*mem); size_t ptr_remain = *mem_size; size_t matches = 0; // Special case for replacing nothing if (from_size == 0) { if (n_replaced) { *n_replaced = 0; } return oc::success(); } while ((n == 0 || matches < n) && (ptr = static_cast<char *>( mb_memmem(ptr, ptr_remain, from, from_size)))) { // Resize buffer to accomodate data if (buf_size >= SIZE_MAX - static_cast<size_t>(ptr - base_ptr) || buf_size + static_cast<size_t>(ptr - base_ptr) >= SIZE_MAX - to_size) { free(buf); return std::make_error_code(std::errc::value_too_large); } size_t new_buf_size = buf_size + static_cast<size_t>(ptr - base_ptr) + to_size; auto new_buf = static_cast<char *>(realloc(buf, new_buf_size)); if (!new_buf) { free(buf); return ec_from_errno(); } target_ptr = new_buf + buf_size; // Copy data left of the match target_ptr = _mb_mempcpy(target_ptr, base_ptr, static_cast<size_t>(ptr - base_ptr)); // Copy replacement target_ptr = _mb_mempcpy(target_ptr, to, to_size); buf = new_buf; buf_size = new_buf_size; ptr += from_size; ptr_remain -= static_cast<size_t>(ptr - base_ptr); base_ptr = ptr; ++matches; } // Copy remainder of string if (ptr_remain > 0) { if (buf_size >= SIZE_MAX - ptr_remain) { free(buf); return std::make_error_code(std::errc::value_too_large); } size_t new_buf_size = buf_size + ptr_remain; auto new_buf = static_cast<char *>(realloc(buf, new_buf_size)); if (!new_buf) { free(buf); return ec_from_errno(); } target_ptr = new_buf + buf_size; target_ptr = _mb_mempcpy(target_ptr, base_ptr, ptr_remain); buf = new_buf; buf_size = new_buf_size; } free(*mem); *mem = buf; *mem_size = buf_size; if (n_replaced) { *n_replaced = matches; } return oc::success(); }