chromium/chrome/browser/ash/integration_tests/security_files_integration_test.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <grp.h>
#include <pwd.h>
#include <sys/stat.h>

#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/chromeos/crosier/ash_integration_test.h"
#include "chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h"

namespace {

// Returns the Posix file permissions for a file at `path`. The file is assumed
// to exist.
int GetFilePermissions(const base::FilePath& path) {
  int mode = 0;
  CHECK(base::GetPosixFilePermissions(path, &mode));
  return mode;
}

// Returns the owner of the file at `path`. The file is assumed to exist.
std::string GetFileOwner(const base::FilePath& path) {
  struct stat info;
  int rv = stat(path.MaybeAsASCII().c_str(), &info);
  CHECK_EQ(rv, 0);
  // Look up the owner in the passwd database.
  struct passwd* file_owner = getpwuid(info.st_uid);
  CHECK(file_owner);
  return file_owner->pw_name;
}

// Returns the group of the file at `path`. The file is assumed to exist.
std::string GetFileGroup(const base::FilePath& path) {
  struct stat info;
  int rv = stat(path.MaybeAsASCII().c_str(), &info);
  CHECK_EQ(rv, 0);
  // Look up the file's group in the group database.
  struct group* file_group = getgrgid(info.st_gid);
  CHECK(file_group);
  return file_group->gr_name;
}

class SecurityFilesIntegrationTest : public AshIntegrationTest {
 public:
  SecurityFilesIntegrationTest() {
    // Keep test running after dismissing login screen.
    set_exit_when_last_browser_closes(false);

    login_mixin().SetMode(ChromeOSIntegrationLoginMixin::Mode::kTestLogin);
  }

  ~SecurityFilesIntegrationTest() override = default;

  // AshIntegrationTest:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    AshIntegrationTest::SetUpCommandLine(command_line);
    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
        switches::kUserDataDir, "/home/chronos");
  }
};

IN_PROC_BROWSER_TEST_F(SecurityFilesIntegrationTest, UserFilesLoggedIn) {
  login_mixin().Login();

  ash::test::WaitForPrimaryUserSessionStart();
  EXPECT_TRUE(login_mixin().IsCryptohomeMounted());

  base::ScopedAllowBlockingForTesting allow_blocking;

  // Validate /home/chronos.
  base::FilePath home_chronos("/home/chronos");
  ASSERT_TRUE(base::PathExists(home_chronos));
  EXPECT_EQ(GetFilePermissions(home_chronos), 0755);
  EXPECT_EQ(GetFileOwner(home_chronos), "chronos");

  // Validate /home/chronos/user.
  base::FilePath home_chronos_user("/home/chronos/user");
  ASSERT_TRUE(base::PathExists(home_chronos_user));
  EXPECT_FALSE(GetFilePermissions(home_chronos_user) &
               base::FILE_PERMISSION_WRITE_BY_OTHERS);
  EXPECT_EQ(GetFileOwner(home_chronos_user), "chronos");

  // There is at least one directory matching "u-*".
  base::FileEnumerator enumerator(home_chronos, /*recursive=*/false,
                                  base::FileEnumerator::DIRECTORIES, "u-*");
  int count = 0;
  enumerator.ForEach([&count](const base::FilePath& item) {
    count++;
    EXPECT_FALSE(GetFilePermissions(item) &
                 base::FILE_PERMISSION_WRITE_BY_OTHERS);
  });
  EXPECT_GT(count, 0);

  // Validate contents of /home/chronos.
  base::FileEnumerator enumerator2(
      home_chronos, /*recursive=*/false,
      base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES, "*");
  enumerator2.ForEach([](const base::FilePath& item) {
    // /home/chronos/crash is not other writable.
    if (item == base::FilePath("/home/chronos/crash")) {
      EXPECT_FALSE(GetFilePermissions(item) &
                   base::FILE_PERMISSION_WRITE_BY_OTHERS);
      return;
    }
    // All other subdirectories and files are not group writable and not other
    // writable.
    constexpr int banned = base::FILE_PERMISSION_WRITE_BY_GROUP |
                           base::FILE_PERMISSION_WRITE_BY_OTHERS;
    EXPECT_FALSE(GetFilePermissions(item) & banned) << item.MaybeAsASCII();
  });

  // Validate Downloads.
  base::FilePath downloads("/home/chronos/user/MyFiles/Downloads");
  ASSERT_TRUE(base::PathExists(downloads));
  EXPECT_EQ(GetFilePermissions(downloads), 0750);
  EXPECT_EQ(GetFileOwner(downloads), "chronos");
  EXPECT_EQ(GetFileGroup(downloads), "chronos-access");
}

}  // namespace