1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "brillo/files/safe_fd.h"
6
7 #include <fcntl.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10
11 #include <base/files/file_util.h>
12 #include <base/logging.h>
13 #include <base/posix/eintr_wrapper.h>
14 #include <brillo/files/file_util.h>
15 #include <brillo/files/scoped_dir.h>
16 #include <brillo/syslog_logging.h>
17
18 namespace brillo {
19
20 namespace {
21
MakeErrorResult(SafeFD::Error error)22 SafeFD::SafeFDResult MakeErrorResult(SafeFD::Error error) {
23 return std::make_pair(SafeFD(), error);
24 }
25
MakeSuccessResult(SafeFD && fd)26 SafeFD::SafeFDResult MakeSuccessResult(SafeFD&& fd) {
27 return std::make_pair(std::move(fd), SafeFD::Error::kNoError);
28 }
29
OpenPathComponentInternal(int parent_fd,const std::string & file,int flags,mode_t mode)30 SafeFD::SafeFDResult OpenPathComponentInternal(int parent_fd,
31 const std::string& file,
32 int flags,
33 mode_t mode) {
34 if (file != "/" && file.find("/") != std::string::npos) {
35 return MakeErrorResult(SafeFD::Error::kBadArgument);
36 }
37 SafeFD fd;
38
39 // O_NONBLOCK is used to avoid hanging on edge cases (e.g. a serial port with
40 // flow control, or a FIFO without a writer).
41 if (parent_fd >= 0 || parent_fd == AT_FDCWD) {
42 fd.UnsafeReset(HANDLE_EINTR(openat(parent_fd, file.c_str(),
43 flags | O_NONBLOCK | O_NOFOLLOW, mode)));
44 } else if (file == "/") {
45 fd.UnsafeReset(HANDLE_EINTR(open(
46 file.c_str(), flags | O_DIRECTORY | O_NONBLOCK | O_NOFOLLOW, mode)));
47 }
48
49 if (!fd.is_valid()) {
50 // open(2) fails with ELOOP when the last component of the |path| is a
51 // symlink. It fails with ENXIO when |path| is a FIFO and |flags| is for
52 // writing because of the O_NONBLOCK flag added above.
53 switch (errno) {
54 case ENOENT:
55 // Do not write to the log because opening a non-existent file is a
56 // frequent occurrence.
57 return MakeErrorResult(SafeFD::Error::kDoesNotExist);
58 case ELOOP:
59 // PLOG prints something along the lines of the symlink depth being too
60 // great which is is misleading so LOG is used instead.
61 LOG(ERROR) << "Symlink detected! failed to open \"" << file
62 << "\" safely.";
63 return MakeErrorResult(SafeFD::Error::kSymlinkDetected);
64 case EISDIR:
65 PLOG(ERROR) << "Directory detected! failed to open \"" << file
66 << "\" safely";
67 return MakeErrorResult(SafeFD::Error::kWrongType);
68 case ENOTDIR:
69 PLOG(ERROR) << "Not a directory! failed to open \"" << file
70 << "\" safely";
71 return MakeErrorResult(SafeFD::Error::kWrongType);
72 case ENXIO:
73 PLOG(ERROR) << "FIFO detected! failed to open \"" << file
74 << "\" safely";
75 return MakeErrorResult(SafeFD::Error::kWrongType);
76 default:
77 PLOG(ERROR) << "Failed to open \"" << file << '"';
78 return MakeErrorResult(SafeFD::Error::kIOError);
79 }
80 }
81
82 // Remove the O_NONBLOCK flag unless the original |flags| have it.
83 if ((flags & O_NONBLOCK) == 0) {
84 flags = fcntl(fd.get(), F_GETFL);
85 if (flags == -1) {
86 PLOG(ERROR) << "Failed to get fd flags for " << file;
87 return MakeErrorResult(SafeFD::Error::kIOError);
88 }
89 if (fcntl(fd.get(), F_SETFL, flags & ~O_NONBLOCK)) {
90 PLOG(ERROR) << "Failed to set fd flags for " << file;
91 return MakeErrorResult(SafeFD::Error::kIOError);
92 }
93 }
94
95 return MakeSuccessResult(std::move(fd));
96 }
97
OpenSafelyInternal(int parent_fd,const base::FilePath & path,int flags,mode_t mode)98 SafeFD::SafeFDResult OpenSafelyInternal(int parent_fd,
99 const base::FilePath& path,
100 int flags,
101 mode_t mode) {
102 std::vector<std::string> components;
103 path.GetComponents(&components);
104
105 auto itr = components.begin();
106 if (itr == components.end()) {
107 LOG(ERROR) << "A path is required.";
108 return MakeErrorResult(SafeFD::Error::kBadArgument);
109 }
110
111 SafeFD::SafeFDResult child_fd;
112 int parent_flags = flags | O_NONBLOCK | O_RDONLY | O_DIRECTORY | O_PATH;
113 for (; itr + 1 != components.end(); ++itr) {
114 child_fd = OpenPathComponentInternal(parent_fd, *itr, parent_flags, 0);
115 // Operation failed, so directly return the error result.
116 if (!child_fd.first.is_valid()) {
117 return child_fd;
118 }
119 parent_fd = child_fd.first.get();
120 }
121
122 return OpenPathComponentInternal(parent_fd, *itr, flags, mode);
123 }
124
CheckAttributes(int fd,mode_t permissions,uid_t uid,gid_t gid)125 SafeFD::Error CheckAttributes(int fd,
126 mode_t permissions,
127 uid_t uid,
128 gid_t gid) {
129 struct stat fd_attributes;
130 if (fstat(fd, &fd_attributes) != 0) {
131 PLOG(ERROR) << "fstat failed";
132 return SafeFD::Error::kIOError;
133 }
134
135 if (fd_attributes.st_uid != uid) {
136 LOG(ERROR) << "Owner uid is " << fd_attributes.st_uid << " instead of "
137 << uid;
138 return SafeFD::Error::kWrongUID;
139 }
140
141 if (fd_attributes.st_gid != gid) {
142 LOG(ERROR) << "Owner gid is " << fd_attributes.st_gid << " instead of "
143 << gid;
144 return SafeFD::Error::kWrongGID;
145 }
146
147 if ((0777 & (fd_attributes.st_mode ^ permissions)) != 0) {
148 mode_t mask = umask(0);
149 umask(mask);
150 LOG(ERROR) << "Permissions are " << std::oct
151 << (0777 & fd_attributes.st_mode) << " instead of "
152 << (0777 & permissions) << ". Umask is " << std::oct << mask
153 << std::dec;
154 return SafeFD::Error::kWrongPermissions;
155 }
156
157 return SafeFD::Error::kNoError;
158 }
159
GetFileSize(int fd,size_t * file_size)160 SafeFD::Error GetFileSize(int fd, size_t* file_size) {
161 struct stat fd_attributes;
162 if (fstat(fd, &fd_attributes) != 0) {
163 return SafeFD::Error::kIOError;
164 }
165
166 *file_size = fd_attributes.st_size;
167 return SafeFD::Error::kNoError;
168 }
169
170 } // namespace
171
IsError(SafeFD::Error err)172 bool SafeFD::IsError(SafeFD::Error err) {
173 return err != Error::kNoError;
174 }
175
176 const char* SafeFD::RootPath = "/";
177
Root()178 SafeFD::SafeFDResult SafeFD::Root() {
179 SafeFD::SafeFDResult root =
180 OpenPathComponentInternal(-1, "/", O_DIRECTORY, 0);
181 if (strcmp(SafeFD::RootPath, "/") == 0) {
182 return root;
183 }
184
185 if (!root.first.is_valid()) {
186 LOG(ERROR) << "Failed to open root directory!";
187 return root;
188 }
189 return root.first.OpenExistingDir(base::FilePath(SafeFD::RootPath));
190 }
191
SetRootPathForTesting(const char * new_root_path)192 void SafeFD::SetRootPathForTesting(const char* new_root_path) {
193 SafeFD::RootPath = new_root_path;
194 }
195
get() const196 int SafeFD::get() const {
197 return fd_.get();
198 }
199
is_valid() const200 bool SafeFD::is_valid() const {
201 return fd_.is_valid();
202 }
203
reset()204 void SafeFD::reset() {
205 return fd_.reset();
206 }
207
UnsafeReset(int fd)208 void SafeFD::UnsafeReset(int fd) {
209 return fd_.reset(fd);
210 }
211
Write(const char * data,size_t size)212 SafeFD::Error SafeFD::Write(const char* data, size_t size) {
213 if (!fd_.is_valid()) {
214 return SafeFD::Error::kNotInitialized;
215 }
216 errno = 0;
217 if (!base::WriteFileDescriptor(fd_.get(), data, size)) {
218 PLOG(ERROR) << "Failed to write to file";
219 return SafeFD::Error::kIOError;
220 }
221
222 if (HANDLE_EINTR(ftruncate(fd_.get(), size)) != 0) {
223 PLOG(ERROR) << "Failed to truncate file";
224 return SafeFD::Error::kIOError;
225 }
226 return SafeFD::Error::kNoError;
227 }
228
ReadContents(size_t max_size)229 std::pair<std::vector<char>, SafeFD::Error> SafeFD::ReadContents(
230 size_t max_size) {
231 std::vector<char> buffer;
232 if (!fd_.is_valid()) {
233 return std::make_pair(std::move(buffer), SafeFD::Error::kNotInitialized);
234 }
235
236 size_t file_size = 0;
237 SafeFD::Error err = GetFileSize(fd_.get(), &file_size);
238 if (IsError(err)) {
239 return std::make_pair(std::move(buffer), err);
240 }
241
242 if (file_size > max_size) {
243 return std::make_pair(std::move(buffer), SafeFD::Error::kExceededMaximum);
244 }
245
246 buffer.resize(file_size);
247
248 err = Read(buffer.data(), buffer.size());
249 if (IsError(err)) {
250 buffer.clear();
251 }
252 return std::make_pair(std::move(buffer), err);
253 }
254
Read(char * data,size_t size)255 SafeFD::Error SafeFD::Read(char* data, size_t size) {
256 if (!fd_.is_valid()) {
257 return SafeFD::Error::kNotInitialized;
258 }
259
260 if (!base::ReadFromFD(fd_.get(), data, size)) {
261 PLOG(ERROR) << "Failed to read file";
262 return SafeFD::Error::kIOError;
263 }
264 return SafeFD::Error::kNoError;
265 }
266
OpenExistingFile(const base::FilePath & path,int flags)267 SafeFD::SafeFDResult SafeFD::OpenExistingFile(const base::FilePath& path,
268 int flags) {
269 if (!fd_.is_valid()) {
270 return MakeErrorResult(SafeFD::Error::kNotInitialized);
271 }
272
273 return OpenSafelyInternal(get(), path, flags, 0 /*mode*/);
274 }
275
OpenExistingDir(const base::FilePath & path,int flags)276 SafeFD::SafeFDResult SafeFD::OpenExistingDir(const base::FilePath& path,
277 int flags) {
278 if (!fd_.is_valid()) {
279 return MakeErrorResult(SafeFD::Error::kNotInitialized);
280 }
281
282 return OpenSafelyInternal(get(), path, O_DIRECTORY | flags /*flags*/,
283 0 /*mode*/);
284 }
285
MakeFile(const base::FilePath & path,mode_t permissions,uid_t uid,gid_t gid,int flags)286 SafeFD::SafeFDResult SafeFD::MakeFile(const base::FilePath& path,
287 mode_t permissions,
288 uid_t uid,
289 gid_t gid,
290 int flags) {
291 if (!fd_.is_valid()) {
292 return MakeErrorResult(SafeFD::Error::kNotInitialized);
293 }
294
295 // Open (and create if necessary) the parent directory.
296 base::FilePath dir_name = path.DirName();
297 SafeFD::SafeFDResult parent_dir;
298 int parent_dir_fd = get();
299 if (!dir_name.empty() &&
300 dir_name.value() != base::FilePath::kCurrentDirectory) {
301 // Apply execute permission where read permission are present for parent
302 // directories.
303 int dir_permissions = permissions | ((permissions & 0444) >> 2);
304 parent_dir =
305 MakeDir(dir_name, dir_permissions, uid, gid, O_RDONLY | O_CLOEXEC);
306 if (!parent_dir.first.is_valid()) {
307 return parent_dir;
308 }
309 parent_dir_fd = parent_dir.first.get();
310 }
311
312 // If file already exists, validate permissions.
313 SafeFDResult file = OpenPathComponentInternal(
314 parent_dir_fd, path.BaseName().value(), flags, permissions /*mode*/);
315 if (file.first.is_valid()) {
316 SafeFD::Error err =
317 CheckAttributes(file.first.get(), permissions, uid, gid);
318 if (IsError(err)) {
319 return MakeErrorResult(err);
320 }
321 return file;
322 } else if (errno != ENOENT) {
323 return file;
324 }
325
326 // The file does exist, create it and set the ownership.
327 file =
328 OpenPathComponentInternal(parent_dir_fd, path.BaseName().value(),
329 O_CREAT | O_EXCL | flags, permissions /*mode*/);
330 if (!file.first.is_valid()) {
331 return file;
332 }
333 if (HANDLE_EINTR(fchown(file.first.get(), uid, gid)) != 0) {
334 PLOG(ERROR) << "Failed to set ownership in MakeFile() for \""
335 << path.value() << '"';
336 return MakeErrorResult(SafeFD::Error::kIOError);
337 }
338 return file;
339 }
340
MakeDir(const base::FilePath & path,mode_t permissions,uid_t uid,gid_t gid,int flags)341 SafeFD::SafeFDResult SafeFD::MakeDir(const base::FilePath& path,
342 mode_t permissions,
343 uid_t uid,
344 gid_t gid,
345 int flags) {
346 if (!fd_.is_valid()) {
347 return MakeErrorResult(SafeFD::Error::kNotInitialized);
348 }
349
350 std::vector<std::string> components;
351 path.GetComponents(&components);
352 if (components.empty()) {
353 LOG(ERROR) << "Called MakeDir() with an empty path";
354 return MakeErrorResult(SafeFD::Error::kBadArgument);
355 }
356
357 // Walk the path creating directories as necessary.
358 SafeFD dir;
359 SafeFDResult child_dir;
360 int parent_dir_fd = get();
361 int dir_flags = O_NONBLOCK | O_DIRECTORY | O_PATH;
362 bool made_dir = false;
363 for (const auto& component : components) {
364 if (mkdirat(parent_dir_fd, component.c_str(), permissions) != 0) {
365 if (errno != EEXIST) {
366 PLOG(ERROR) << "Failed to mkdirat() " << component << ": full_path=\""
367 << path.value() << '"';
368 return MakeErrorResult(SafeFD::Error::kIOError);
369 }
370 } else {
371 made_dir = true;
372 }
373
374 // For the last component in the path, use the flags provided by the caller.
375 if (&component == &components.back()) {
376 dir_flags = flags | O_DIRECTORY;
377 }
378 child_dir = OpenPathComponentInternal(parent_dir_fd, component, dir_flags,
379 0 /*mode*/);
380 if (!child_dir.first.is_valid()) {
381 return child_dir;
382 }
383
384 dir = std::move(child_dir.first);
385 parent_dir_fd = dir.get();
386 }
387
388 if (made_dir) {
389 // If the directory was created, set the ownership.
390 if (HANDLE_EINTR(fchown(dir.get(), uid, gid)) != 0) {
391 PLOG(ERROR) << "Failed to set ownership in MakeDir() for \""
392 << path.value() << '"';
393 return MakeErrorResult(SafeFD::Error::kIOError);
394 }
395 }
396 // If the directory already existed, validate the permissions.
397 SafeFD::Error err = CheckAttributes(dir.get(), permissions, uid, gid);
398 if (IsError(err)) {
399 return MakeErrorResult(err);
400 }
401
402 return MakeSuccessResult(std::move(dir));
403 }
404
Link(const SafeFD & source_dir,const std::string & source_name,const std::string & destination_name)405 SafeFD::Error SafeFD::Link(const SafeFD& source_dir,
406 const std::string& source_name,
407 const std::string& destination_name) {
408 if (!fd_.is_valid() || !source_dir.is_valid()) {
409 return SafeFD::Error::kNotInitialized;
410 }
411
412 SafeFD::Error err = IsValidFilename(source_name);
413 if (IsError(err)) {
414 return err;
415 }
416
417 err = IsValidFilename(destination_name);
418 if (IsError(err)) {
419 return err;
420 }
421
422 if (HANDLE_EINTR(linkat(source_dir.get(), source_name.c_str(), fd_.get(),
423 destination_name.c_str(), 0)) != 0) {
424 PLOG(ERROR) << "Failed to link \"" << destination_name << "\"";
425 return SafeFD::Error::kIOError;
426 }
427 return SafeFD::Error::kNoError;
428 }
429
Unlink(const std::string & name)430 SafeFD::Error SafeFD::Unlink(const std::string& name) {
431 if (!fd_.is_valid()) {
432 return SafeFD::Error::kNotInitialized;
433 }
434
435 SafeFD::Error err = IsValidFilename(name);
436 if (IsError(err)) {
437 return err;
438 }
439
440 if (HANDLE_EINTR(unlinkat(fd_.get(), name.c_str(), 0 /*flags*/)) != 0) {
441 PLOG(ERROR) << "Failed to unlink \"" << name << "\"";
442 return SafeFD::Error::kIOError;
443 }
444 return SafeFD::Error::kNoError;
445 }
446
Rmdir(const std::string & name,bool recursive,size_t max_depth,bool keep_going)447 SafeFD::Error SafeFD::Rmdir(const std::string& name,
448 bool recursive,
449 size_t max_depth,
450 bool keep_going) {
451 if (!fd_.is_valid()) {
452 return SafeFD::Error::kNotInitialized;
453 }
454
455 if (max_depth == 0) {
456 return SafeFD::Error::kExceededMaximum;
457 }
458
459 SafeFD::Error err = IsValidFilename(name);
460 if (IsError(err)) {
461 return err;
462 }
463
464 SafeFD::Error last_err = SafeFD::Error::kNoError;
465
466 if (recursive) {
467 SafeFD dir_fd;
468 std::tie(dir_fd, err) =
469 OpenPathComponentInternal(fd_.get(), name, O_DIRECTORY, 0);
470 if (!dir_fd.is_valid()) {
471 return err;
472 }
473
474 // The ScopedDIR takes ownership of this so dup_fd is not scoped on its own.
475 int dup_fd = dup(dir_fd.get());
476 if (dup_fd < 0) {
477 PLOG(ERROR) << "dup failed";
478 return SafeFD::Error::kIOError;
479 }
480
481 ScopedDIR dir(fdopendir(dup_fd));
482 if (!dir.is_valid()) {
483 PLOG(ERROR) << "fdopendir failed";
484 close(dup_fd);
485 return SafeFD::Error::kIOError;
486 }
487
488 struct stat dir_info;
489 if (fstat(dir_fd.get(), &dir_info) != 0) {
490 return SafeFD::Error::kIOError;
491 }
492
493 errno = 0;
494 const dirent* entry = HANDLE_EINTR_IF_EQ(readdir(dir.get()), nullptr);
495 while (entry != nullptr) {
496 SafeFD::Error err = [&]() {
497 if (strcmp(entry->d_name, ".") == 0 ||
498 strcmp(entry->d_name, "..") == 0) {
499 return SafeFD::Error::kNoError;
500 }
501
502 struct stat child_info;
503 if (fstatat(dir_fd.get(), entry->d_name, &child_info,
504 AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW) != 0) {
505 return SafeFD::Error::kIOError;
506 }
507
508 if (child_info.st_dev != dir_info.st_dev) {
509 return SafeFD::Error::kBoundaryDetected;
510 }
511
512 if (entry->d_type != DT_DIR) {
513 return dir_fd.Unlink(entry->d_name);
514 }
515
516 return dir_fd.Rmdir(entry->d_name, true, max_depth - 1, keep_going);
517 }();
518
519 if (IsError(err)) {
520 if (!keep_going) {
521 return err;
522 }
523 last_err = err;
524 }
525
526 errno = 0;
527 entry = HANDLE_EINTR_IF_EQ(readdir(dir.get()), nullptr);
528 }
529 if (errno != 0) {
530 PLOG(ERROR) << "readdir failed";
531 return SafeFD::Error::kIOError;
532 }
533 }
534
535 if (HANDLE_EINTR(unlinkat(fd_.get(), name.c_str(), AT_REMOVEDIR)) != 0) {
536 PLOG(ERROR) << "unlinkat failed";
537 if (errno == ENOTDIR) {
538 return SafeFD::Error::kWrongType;
539 }
540 // If there was an error during the recursive delete, we expect unlink
541 // to fail with ENOTEMPTY and we bubble the error from recursion
542 // instead.
543 if (IsError(last_err) && errno == ENOTEMPTY) {
544 return last_err;
545 }
546 return SafeFD::Error::kIOError;
547 }
548
549 return last_err;
550 }
551
552 } // namespace brillo
553