Support setting file mode

File mode of writable AuthFsEntry can now be changed. The mode is
maintain privately in authfs, but also pass through to fd_server.

Note that this change only aims to support getting/setting the file
mode. The mode is not currently used for ACL check.

Bug: 205169366
Test: atest AuthFsHostTest
Test: atest ComposHostTestCases
Test: composd_cmd forced-odrefresh
      # exit 80 without ART hack, with permissive SELinux
Change-Id: I2405baedae9ba2be5e84eb84d3228f7be080f8c6
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index 66c943e..c2206c8 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -17,8 +17,9 @@
 use anyhow::Result;
 use log::error;
 use nix::{
-    dir::Dir, errno::Errno, fcntl::openat, fcntl::OFlag, sys::stat::mkdirat, sys::stat::Mode,
-    sys::statvfs::statvfs, sys::statvfs::Statvfs, unistd::unlinkat, unistd::UnlinkatFlags,
+    dir::Dir, errno::Errno, fcntl::openat, fcntl::OFlag, sys::stat::fchmod, sys::stat::mkdirat,
+    sys::stat::mode_t, sys::stat::Mode, sys::statvfs::statvfs, sys::statvfs::Statvfs,
+    unistd::unlinkat, unistd::UnlinkatFlags,
 };
 use std::cmp::min;
 use std::collections::{btree_map, BTreeMap};
@@ -39,17 +40,8 @@
 };
 use binder_common::{new_binder_exception, new_binder_service_specific_error};
 
-fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
-    offset.try_into().map_err(|_| new_errno_error(Errno::EINVAL))
-}
-
-fn validate_and_cast_size(size: i32) -> Result<usize, Status> {
-    if size > MAX_REQUESTING_DATA {
-        Err(new_errno_error(Errno::EFBIG))
-    } else {
-        size.try_into().map_err(|_| new_errno_error(Errno::EINVAL))
-    }
-}
+/// Bitflags of forbidden file mode, e.g. setuid, setgid and sticky bit.
+const FORBIDDEN_MODES: Mode = Mode::from_bits_truncate(!0o777);
 
 /// Configuration of a file descriptor to be served/exposed/shared.
 pub enum FdConfig {
@@ -288,19 +280,20 @@
         })
     }
 
-    fn createFileInDirectory(&self, dir_fd: i32, basename: &str) -> BinderResult<i32> {
+    fn createFileInDirectory(&self, dir_fd: i32, basename: &str, mode: i32) -> BinderResult<i32> {
         validate_basename(basename)?;
 
         self.insert_new_fd(dir_fd, |config| match config {
             FdConfig::InputDir(_) => Err(new_errno_error(Errno::EACCES)),
             FdConfig::OutputDir(dir) => {
+                let mode = validate_file_mode(mode)?;
                 let new_fd = openat(
                     dir.as_raw_fd(),
                     basename,
                     // TODO(205172873): handle the case when the file already exist, e.g. truncate
                     // or fail, and possibly allow the client to specify. For now, always truncate.
                     OFlag::O_CREAT | OFlag::O_RDWR | OFlag::O_TRUNC,
-                    Mode::S_IRUSR | Mode::S_IWUSR,
+                    mode,
                 )
                 .map_err(new_errno_error)?;
                 // SAFETY: new_fd is just created and not an error.
@@ -311,13 +304,19 @@
         })
     }
 
-    fn createDirectoryInDirectory(&self, dir_fd: i32, basename: &str) -> BinderResult<i32> {
+    fn createDirectoryInDirectory(
+        &self,
+        dir_fd: i32,
+        basename: &str,
+        mode: i32,
+    ) -> BinderResult<i32> {
         validate_basename(basename)?;
 
         self.insert_new_fd(dir_fd, |config| match config {
             FdConfig::InputDir(_) => Err(new_errno_error(Errno::EACCES)),
             FdConfig::OutputDir(_) => {
-                mkdirat(dir_fd, basename, Mode::S_IRWXU).map_err(new_errno_error)?;
+                let mode = validate_file_mode(mode)?;
+                mkdirat(dir_fd, basename, mode).map_err(new_errno_error)?;
                 let new_dir = Dir::openat(
                     dir_fd,
                     basename,
@@ -359,6 +358,16 @@
         })
     }
 
+    fn chmod(&self, fd: i32, mode: i32) -> BinderResult<()> {
+        self.handle_fd(fd, |config| match config {
+            FdConfig::ReadWrite(_) | FdConfig::OutputDir(_) => {
+                let mode = validate_file_mode(mode)?;
+                fchmod(fd, mode).map_err(new_errno_error)
+            }
+            _ => Err(new_errno_error(Errno::EACCES)),
+        })
+    }
+
     fn statfs(&self) -> BinderResult<FsStat> {
         let st = statvfs("/data").map_err(new_errno_error)?;
         try_into_fs_stat(st).map_err(|_e| new_errno_error(Errno::EINVAL))
@@ -395,6 +404,18 @@
     Ok(new_file)
 }
 
+fn validate_and_cast_offset(offset: i64) -> Result<u64, Status> {
+    offset.try_into().map_err(|_| new_errno_error(Errno::EINVAL))
+}
+
+fn validate_and_cast_size(size: i32) -> Result<usize, Status> {
+    if size > MAX_REQUESTING_DATA {
+        Err(new_errno_error(Errno::EFBIG))
+    } else {
+        size.try_into().map_err(|_| new_errno_error(Errno::EINVAL))
+    }
+}
+
 fn validate_basename(name: &str) -> BinderResult<()> {
     if name.contains(MAIN_SEPARATOR) {
         Err(new_errno_error(Errno::EINVAL))
@@ -402,3 +423,12 @@
         Ok(())
     }
 }
+
+fn validate_file_mode(mode: i32) -> BinderResult<Mode> {
+    let mode = Mode::from_bits(mode as mode_t).ok_or_else(|| new_errno_error(Errno::EINVAL))?;
+    if mode.intersects(FORBIDDEN_MODES) {
+        Err(new_errno_error(Errno::EPERM))
+    } else {
+        Ok(mode)
+    }
+}