#文件权限 #fs-file #权限 #文件路径 #文件 #fs #隐私

fs-mistrust

确保文件只能由可信用户读取或写入

23 个版本

0.7.12 2024 年 8 月 1 日
0.7.11 2024 年 6 月 5 日
0.7.10 2024 年 4 月 30 日
0.7.8 2024 年 3 月 4 日
0.3.0 2022 年 6 月 24 日

#28文件系统

Download history 661/week @ 2024-05-01 604/week @ 2024-05-08 499/week @ 2024-05-15 1052/week @ 2024-05-22 695/week @ 2024-05-29 635/week @ 2024-06-05 1272/week @ 2024-06-12 1524/week @ 2024-06-19 1594/week @ 2024-06-26 633/week @ 2024-07-03 610/week @ 2024-07-10 1118/week @ 2024-07-17 1186/week @ 2024-07-24 1828/week @ 2024-07-31 1366/week @ 2024-08-07 1301/week @ 2024-08-14

5,777 每月下载量
用于 41 个 Crates(14 个直接使用)

MIT/Apache

160KB
2.5K SLoC

fs-mistrust

检查文件权限是否私有。

此库提供了一组功能来检查文件和目录的权限,以确保它们是有效私有的——也就是说,它们只能被可信[^1]用户读取或写入。

这种检查可以保护您的用户数据免受误配置的影响,例如,他们不小心使主目录对所有人可写,或者他们在一个由其他用户拥有的目录中使用了符号链接。

此库中的检查试图保证,一旦一个路径被证明是私有的,任何非可信用户的行为都不能使该路径变得私有。可信用户仍然可以在检查后更改路径。因此,如果您担心可信用户更改文件系统可能引起的时间检查/使用问题,您可能需要使用其他机制。

有关限制,请参阅下面的 限制 部分。

[^1]: 我们在这里使用“可信”这个词是指计算机安全意义上的:如果一个用户有机会破坏我们的安全保证,那么这个用户就是“可信”的。例如,在 Unix 环境中,“root”就是“可信”的,无论您是否真正信任他们。

检查权限有什么困难吗?

假设我们想知道一个给定的路径是否可以被不可信用户读取或修改。这比听起来要复杂

  • 即使文件本身的权限是正确的,我们还需要检查包含该文件的目录的权限,因为它们可能允许不可信用户替换文件或更改其权限。
  • 同样,我们需要检查该目录的父目录的权限,因为它们可能允许不可信用户替换目录或更改其权限。(以此类推!)
  • 定义“可信用户”可能会很棘手。在Unix系统中,我们通常认为每个用户都信任自己,而root(UID 0)是可信的。但是很难说哪些是可信的:即使一个给定的组今天只包含可信用户,也没有操作系统级别的保证,未来不会将不可信用户添加到该组中。
  • 符号链接增加了另一层混淆。如果你正在检查的路径中存在任何符号链接,那么你需要检查包含符号链接的目录的权限,然后是目标路径的权限,以及其所有祖先的权限。
  • 许多程序首先对正在检查的路径进行规范化,删除所有..和符号链接。这足以判断最终文件是否可以被不可信用户修改,但不足以判断路径是否可以被不可信用户修改。如果路径中存在可修改的符号链接,或者在路径解析的任何阶段,可以修改该符号链接的人可以更改该路径指向的文件。
  • 即使你已经检查了一个目录只能由可信用户写入,这并不意味着该目录中的对象只能由可信用户写入。这些对象可能是指向文件系统上其他(可写入性更高)位置的符号链接;或者它们可能是通过存储在文件系统其他地方的硬链接可访问的。

不同的程序以不同的方式尝试解决这个问题,通常理由很少。这个crate试图为文件隐私检查和执行提供一个合理的实现,并在其源代码中提供清晰的解释,说明为什么它以这种方式运行。

我们实际上做了什么

为了确保文件解析过程中的每一步都被检查,我们在自己的系统中模拟这个过程。我们检查提供的路径中的每个组件,以查看它是否可以被不可信用户修改。如果我们遇到一个或多个符号链接,那么我们将解析由这些符号链接添加的路径的每个组件,直到我们最终到达目标。

实际上,我们正在模拟realpath(或者如果你更喜欢,fs::canonicalize),并检查我们在这样做过程中接触到的文件系统每个部分的权限,以查看谁有权更改我们的目标文件或导致我们到达该文件的过程。

对于组,我们使用以下启发式方法:如果存在与当前用户同名的组,并且当前用户属于该组,我们假设该组是可信的。否则,我们将所有组视为不可信。

示例

简单情况

确保目录只能由我们读取或写入(简单情况)

use fs_mistrust::Mistrust;
match Mistrust::new().check_directory("/home/itchy/.local/hat-swap") {
    Ok(()) => println!("directory is good"),
    Err(e) => println!("problem with our hat-swap directory: {}", e),
}

如上所述,但创建目录及其父目录(如果它们不存在的话)。

use fs_mistrust::Mistrust;
match Mistrust::new().make_directory("/home/itchy/.local/hat-swap") {
    Ok(()) => println!("directory exists (or was created without trouble"),
    Err(e) => println!("problem with our hat-swap directory: {}", e),
}

配置Mistrust

您可以调整Mistrust对象来更改它允许的内容

# fn main() -> Result<(), fs_mistrust::Error> {
use fs_mistrust::Mistrust;

let my_mistrust = Mistrust::builder()
    // Assume that our home directory and its parents are all well-configured.
    .ignore_prefix("/home/doze/")
    // Assume that a given group will only contain trusted users (this feature is only
    // available on Unix-like platforms).
    // .trust_group(413)
    .build()?;
# Ok(())
# }

有关更多选项,请参阅Mistrust

使用Verifier进行更细致的检查

对于对特定检查的更细致控制,您可以使用Verifier API。与通常需要为多个请求配置的Mistrust不同,Verifier中的更改通常只适用于一次请求。

# fn main() -> Result<(), fs_mistrust::Error> {
use fs_mistrust::Mistrust;
let mistrust = Mistrust::new();

// Require that an object is a regular file; allow it to be world-
// readable.
mistrust
    .verifier()
    .permit_readable()
    .require_file()
    .check("/home/trace/.path_cfg")?;

// Make sure that a directory _and all of its contents_ are private.
// Create the directory if it does not exist.
// Return an error object containing _all_ of the problems discovered.
mistrust
    .verifier()
    .require_directory()
    .check_content()
    .all_errors()
    .make_directory("/home/trace/private_keys/");
# Ok(())
# }

请参阅Verifier获取更多选项。

使用CheckedDir确保安全性。

您可以使用CheckedDir API 确保不仅目录是私有的,而且对其内容的所有访问都继续验证和强制执行它们的权限。

# fn main() -> Result<(), fs_mistrust::Error> {
use fs_mistrust::{Mistrust, CheckedDir};
use std::fs::{File, OpenOptions};
let dir = Mistrust::new()
    .verifier()
    .secure_dir("/Users/clover/riddles")?;

// You can use the CheckedDir object to access files and directories.
// All of these must be relative paths within the path you used to
// build the CheckedDir.
dir.make_directory("timelines")?;
let file = dir.open("timelines/vault-destroyed.md",
    OpenOptions::new().write(true).create(true))?;
// (... use file...)
# Ok(())
# }

限制

如上所述,这个crate只检查路径是否可以被非信任用户更改。路径检查后,信任用户仍然可以更改其权限。(例如,用户可以使他们的家目录对所有用户可写。)此crate不会尝试防御这种类型的检查时间/使用时间问题。

我们目前假设一个相当普通的Unix环境:我们会容忍其他系统,但实际上我们不会查看这些系统的任何细节

  • Windows安全性(ACLs、安全描述符等)
  • SELinux能力
  • POSIX(和其他)ACLs。

当我们在检查目标目录内项目的权限时(使用Verifier::check_contentCheckedDir),我们会使用一个不太准确的启发式方法:我们继续禁止未受信任的可写目录和文件,但仍然允许可读的,即使我们坚持目标目录本身必须不可读。在可读对象具有硬链接的情况下,这过于宽容:如果文件在其他地方有硬链接,则未受信任的用户可以读取它。在没有硬链接的可写对象的情况下,这也过于严格:如果未受信任的用户没有访问这些对象的路径,他们实际上不能写入它们。

在Windows上,我们接受所有文件权限和所有者。

我们不检查挂载点和文件系统的设备的隐私。(例如,我们不区分本地管理员和远程文件系统的管理员。我们也不区分本地文件系统和不安全的网络文件系统。)

此代码尚未在setuid环境中经过审计,几乎肯定存在安全漏洞。

这是一款相当新的软件,尚未经过审计。

上述所有问题都被认为是“如果可行,则应该修复”。

致谢

这里执行的检查列表受到了OpenSSH的safe_path、GnuPG的check_permissions和Tor的check_private_dir列表的启发。所有错误都是我自己的。

许可:MIT OR Apache-2.0

依赖关系

~0.9–12MB
~82K SLoC