| // Copyright 2021 The ChromiumOS Authors | |
| // 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 <memory> | |
| #include <sys/fanotify.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/stat.h> | |
| #include <utility> | |
| #include <vector> | |
| #include "base/check.h" | |
| #include "base/logging.h" | |
| #include "base/memory/scoped_refptr.h" | |
| #include "base/posix/eintr_wrapper.h" | |
| #include "base/task/sequenced_task_runner.h" | |
| #include "base/threading/platform_thread.h" | |
| #include "dlp/dlp_metrics.h" | |
| namespace { | |
| // Kill the daemon if not responding in 1 second. | |
| constexpr base::TimeDelta kWatchdogTimeout = base::Milliseconds(1000); | |
| constexpr char kWatchdogName[] = "DLP daemon"; | |
| // TODO(b/259688785): Update fanofity headers to include the struct. | |
| /* Variable length info record following event metadata */ | |
| struct fanotify_event_info_header { | |
| __u8 info_type; | |
| __u8 pad; | |
| __u16 len; | |
| }; | |
| // TODO(b/259688785): Update fanofity headers to include the struct. | |
| /* | |
| * Unique file identifier info record. | |
| * This structure is used for records of types FAN_EVENT_INFO_TYPE_FID, | |
| * FAN_EVENT_INFO_TYPE_DFID and FAN_EVENT_INFO_TYPE_DFID_NAME. | |
| * For FAN_EVENT_INFO_TYPE_DFID_NAME there is additionally a null terminated | |
| * name immediately after the file handle. | |
| */ | |
| struct fanotify_event_info_fid { | |
| struct fanotify_event_info_header hdr; | |
| __kernel_fsid_t fsid; | |
| /* | |
| * Following is an opaque struct file_handle that can be passed as | |
| * an argument to open_by_handle_at(2). | |
| */ | |
| unsigned char handle[]; | |
| }; | |
| // Converts a statx_timestamp struct to time_t. | |
| time_t ConvertStatxTimestampToTimeT(const struct statx_timestamp& sts) { | |
| struct timespec ts; | |
| ts.tv_sec = sts.tv_sec; | |
| ts.tv_nsec = sts.tv_nsec; | |
| return base::Time::FromTimeSpec(ts).ToTimeT(); | |
| } | |
| } // namespace | |
| namespace dlp { | |
| FanotifyReaderThread::FanotifyReplyWatchdog::FanotifyReplyWatchdog() | |
| : watchdog_(kWatchdogTimeout, kWatchdogName, /*enabled=*/true, this) {} | |
| FanotifyReaderThread::FanotifyReplyWatchdog::~FanotifyReplyWatchdog() = default; | |
| void FanotifyReaderThread::FanotifyReplyWatchdog::Arm() { | |
| watchdog_.Arm(); | |
| } | |
| void FanotifyReaderThread::FanotifyReplyWatchdog::Disarm() { | |
| watchdog_.Disarm(); | |
| } | |
| void FanotifyReaderThread::FanotifyReplyWatchdog::Alarm() { | |
| LOG(ERROR) << "DLP thread hang, watchdog triggered, exiting abnormally"; | |
| _exit(2); | |
| } | |
| 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() { | |
| if (!handle_.is_null()) { | |
| 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_); | |
| // Set constant large buffer size per fanotify man page recommendations. | |
| char buffer[4096]; | |
| 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 fanotify events are available. | |
| int select_result = | |
| HANDLE_EINTR(select(fanotify_fd_ + 1, &rfds, nullptr, nullptr, &tv)); | |
| if (select_result < 0) { | |
| PLOG(WARNING) << "select failed"; | |
| ForwardUMAErrorToParentThread(FanotifyError::kSelectError); | |
| return; | |
| } else if (select_result == 0) { | |
| continue; | |
| } | |
| ssize_t bytes_read = | |
| HANDLE_EINTR(read(fanotify_fd_, buffer, sizeof(buffer))); | |
| if (bytes_read < 0) { | |
| PLOG(WARNING) << "read from fanotify fd failed, possibly exiting"; | |
| // Not reporting UMA because the parent object might already be deleted. | |
| return; | |
| } | |
| fanotify_event_metadata* metadata = | |
| reinterpret_cast<fanotify_event_metadata*>(&buffer[0]); | |
| for (; FAN_EVENT_OK(metadata, bytes_read); | |
| metadata = FAN_EVENT_NEXT(metadata, bytes_read)) { | |
| if (metadata->vers != FANOTIFY_METADATA_VERSION) { | |
| LOG(ERROR) << "mismatch of fanotify metadata version"; | |
| ForwardUMAErrorToParentThread(FanotifyError::kMetadataMismatchError); | |
| return; | |
| } | |
| if (metadata->mask & FAN_OPEN_PERM) { | |
| if (metadata->fd < 0) { | |
| LOG(ERROR) << "invalid file descriptor for OPEN_PERM event"; | |
| ForwardUMAErrorToParentThread( | |
| FanotifyError::kInvalidFileDescriptorError); | |
| continue; | |
| } | |
| base::ScopedFD fd(metadata->fd); | |
| struct statx st; | |
| if (statx(fd.get(), "", AT_EMPTY_PATH, STATX_INO | STATX_BTIME, &st)) { | |
| PLOG(ERROR) << "statx failed"; | |
| ForwardUMAErrorToParentThread(FanotifyError::kFstatError); | |
| AllowRequest(fd.get()); | |
| continue; | |
| } | |
| if (!(st.stx_mask & STATX_BTIME) || !(st.stx_mask & STATX_INO)) { | |
| PLOG(ERROR) << "statx failed"; | |
| ForwardUMAErrorToParentThread(FanotifyError::kFstatError); | |
| AllowRequest(fd.get()); | |
| continue; | |
| } | |
| // If the request is not replied on time, the watchdog will restart | |
| // the daemon. | |
| std::unique_ptr<FanotifyReplyWatchdog> watchdog = | |
| std::make_unique<FanotifyReplyWatchdog>(); | |
| watchdog->Arm(); | |
| parent_task_runner_->PostTask( | |
| FROM_HERE, | |
| base::BindOnce(&Delegate::OnFileOpenRequested, | |
| base::Unretained(delegate_), st.stx_ino, | |
| ConvertStatxTimestampToTimeT(st.stx_btime), | |
| metadata->pid, std::move(fd), std::move(watchdog))); | |
| } else if (metadata->mask & FAN_DELETE_SELF) { | |
| struct file_handle* file_handle; | |
| struct fanotify_event_info_fid* fid; | |
| fid = (struct fanotify_event_info_fid*)(metadata + 1); | |
| // TODO(b/259688785): Update fanofity headers to include the const. | |
| if (fid->hdr.info_type != /*FAN_EVENT_INFO_TYPE_FID=*/1) { | |
| LOG(ERROR) << "expected FID type DELETE_SELF event"; | |
| ForwardUMAErrorToParentThread( | |
| FanotifyError::kUnexpectedEventInfoTypeError); | |
| continue; | |
| } | |
| file_handle = (struct file_handle*)fid->handle; | |
| uint32_t* handle = reinterpret_cast<uint32_t*>(file_handle->f_handle); | |
| if (file_handle->handle_type != /*FILEID_INO32_GEN=*/1) { | |
| LOG(ERROR) << "unexpected file_handle type: " | |
| << file_handle->handle_type; | |
| ForwardUMAErrorToParentThread( | |
| FanotifyError::kUnexpectedFileHandleTypeError); | |
| continue; | |
| } | |
| parent_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&Delegate::OnFileDeleted, | |
| base::Unretained(delegate_), handle[0])); | |
| } else { | |
| LOG(WARNING) << "unexpected fanotify event: " << metadata->mask; | |
| ForwardUMAErrorToParentThread(FanotifyError::kUnknownError); | |
| } | |
| } | |
| } | |
| } | |
| void FanotifyReaderThread::ForwardUMAErrorToParentThread(FanotifyError error) { | |
| parent_task_runner_->PostTask( | |
| FROM_HERE, base::BindOnce(&Delegate::OnFanotifyError, | |
| base::Unretained(delegate_), error)); | |
| } | |
| void FanotifyReaderThread::AllowRequest(int fd) { | |
| struct fanotify_response response = {}; | |
| response.fd = fd; | |
| response.response = FAN_ALLOW; | |
| HANDLE_EINTR(write(fanotify_fd_, &response, sizeof(response))); | |
| } | |
| } // namespace dlp |