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