Beispiel #1
0
/*!
 * \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();
}
Beispiel #7
0
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);
}
Beispiel #8
0
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;
}
Beispiel #9
0
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;
}
Beispiel #10
0
/*!
 * \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);
}
Beispiel #13
0
/*!
 * \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();
}
Beispiel #15
0
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();
}
Beispiel #16
0
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();
}
Beispiel #18
0
/*!
 * \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();
}
Beispiel #19
0
/*!
 * \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();
}