| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "dlp/fanotify_reader_thread.h" |
| |
| #include <fcntl.h> |
| #include <sys/fanotify.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| |
| namespace dlp { |
| |
| FanotifyReaderThread::FanotifyReaderThread( |
| scoped_refptr<base::SequencedTaskRunner> parent_task_runner, |
| Delegate* delegate) |
| : parent_task_runner_(std::move(parent_task_runner)), delegate_(delegate) { |
| CHECK(delegate_); |
| CHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| } |
| |
| FanotifyReaderThread::~FanotifyReaderThread() { |
| base::PlatformThread::Join(handle_); |
| } |
| |
| void FanotifyReaderThread::StartThread(int fanotify_fd) { |
| CHECK(parent_task_runner_->RunsTasksInCurrentSequence()); |
| fanotify_fd_ = fanotify_fd; |
| |
| CHECK(base::PlatformThread::Create(0, this, &handle_)); |
| } |
| |
| void FanotifyReaderThread::ThreadMain() { |
| CHECK(!parent_task_runner_->RunsTasksInCurrentSequence()); |
| base::PlatformThread::SetName("fanotify_reader"); |
| |
| RunLoop(); |
| |
| // TODO(poromov): Gracefully stop the thread and notify. |
| } |
| |
| void FanotifyReaderThread::RunLoop() { |
| CHECK(!parent_task_runner_->RunsTasksInCurrentSequence()); |
| |
| CHECK_LE(0, fanotify_fd_); |
| CHECK_GT(FD_SETSIZE, fanotify_fd_); |
| |
| std::vector<char> buffer(0); |
| while (true) { |
| fd_set rfds; |
| FD_ZERO(&rfds); |
| FD_SET(fanotify_fd_, &rfds); |
| // Re-check file descriptor every second. |
| struct timeval tv; |
| tv.tv_sec = 1; |
| tv.tv_usec = 0; |
| |
| // Wait until some inotify events are available. |
| int select_result = |
| HANDLE_EINTR(select(fanotify_fd_ + 1, &rfds, nullptr, nullptr, &tv)); |
| if (select_result < 0) { |
| PLOG(WARNING) << "select failed"; |
| return; |
| } else if (select_result == 0) { |
| continue; |
| } |
| |
| // Adjust buffer size to current event queue size. |
| int buffer_size; |
| int ioctl_result = |
| HANDLE_EINTR(ioctl(fanotify_fd_, FIONREAD, &buffer_size)); |
| if (ioctl_result != 0) { |
| PLOG(WARNING) << "ioctl failed"; |
| return; |
| } |
| |
| buffer.resize(buffer_size); |
| ssize_t bytes_read = |
| HANDLE_EINTR(read(fanotify_fd_, &buffer[0], buffer_size)); |
| if (bytes_read < 0) { |
| PLOG(WARNING) << "read from fanotify fd failed"; |
| return; |
| } |
| |
| fanotify_event_metadata* metadata = |
| reinterpret_cast<fanotify_event_metadata*>(&buffer[0]); |
| while (FAN_EVENT_OK(metadata, bytes_read)) { |
| if (metadata->vers != FANOTIFY_METADATA_VERSION) { |
| LOG(ERROR) << "mismatch of fanotify metadata version"; |
| return; |
| } |
| if (metadata->fd >= 0) { |
| base::ScopedFD fd(metadata->fd); |
| if (metadata->mask & FAN_OPEN_PERM) { |
| struct stat st; |
| if (fstat(fd.get(), &st)) { |
| PLOG(ERROR) << "fstat failed"; |
| metadata = FAN_EVENT_NEXT(metadata, bytes_read); |
| continue; |
| } |
| |
| parent_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&Delegate::OnFileOpenRequested, |
| base::Unretained(delegate_), st.st_ino, |
| metadata->pid, std::move(fd))); |
| } else { |
| LOG(WARNING) << "unexpected fanotify event: " << metadata->mask; |
| } |
| } |
| metadata = FAN_EVENT_NEXT(metadata, bytes_read); |
| } |
| } |
| } |
| |
| } // namespace dlp |