如何确定路径是否在目录中? (POSIX)

时间:2011-08-20 21:03:38

标签: c posix

在C中,使用POSIX调用,如何确定路径是否在目标目录中?

例如,Web服务器的根目录位于/srv,守护程序为getcwd()。 在解析/index.html的请求时,它会返回/srv/index.html

的内容

如何过滤掉/srv以外的路径请求?

/../etc/passwd/valid/../../etc/passwd, 等

/拆分路径并拒绝任何包含..的数组会破坏有效访问/srv/valid/../index.html

使用系统调用是否有规范的方法?或者我是否需要手动遍历路径并计算目录深度?

3 个答案:

答案 0 :(得分:6)

始终有realpath

  

realpath()函数应从* file_name *指向的路径名派生一个绝对路径名,该路径名解析为同一目录条目,其分辨率不涉及'。' ,'..'或符号链接。

然后将realpath提供的内容与您想要的根目录进行比较,看看它们是否匹配。

您还可以在添加"/srv"之前通过展开双点来手动清理文件名。将传入路径拆分为斜线并逐个遍历。如果你得到一个"."然后将其删除并继续前进;如果你得到"..",那么删除它和前一个组件(注意不要超过列表中的第一个条目);如果您还有其他任何内容,请转到下一个组件。然后将左边的内容与组件之间的斜杠粘贴在一起并添加"/srv/"。因此,如果有人向您提供"/valid/../../etc/passwd",您最终会以"/srv/etc/passwd"结束,而"/where/is/../pancakes/house"将以"/srv/where/pancakes/house"结尾。

这样你就无法到达"/srv"之外(当然除了通过符号链接),传入的"/../.."将与"/"相同(就像在普通文件系统中一样) )。但如果你担心realpath下的符号,你仍然想使用"/srv"

逐个组件使用路径名称还可以打破您呈现给外部世界的布局与实际文件系统布局之间的连接; "/this/that/other/thing"无需在任何地方映射到实际的"/srv/this/that/other/thing"文件,该路径可能只是某种数据库中的键或函数调用的某种命名空间路径。

答案 1 :(得分:2)

要确定文件F是否在目录D中,首先要使用stat D来确定其设备编号和inode编号(struct stat的成员st_dev和st_ino)。

然后使用stat F来确定它是否是目录。如果没有,请调用basename以确定包含它的目录的名称。将G设置为此目录的名称。如果F已经是目录,请设置G = F.

现在,当且仅当G在D之内时,F在D内。接下来我们有一个循环。

while (1) {
  if (samefile(d_statinfo.d_dev, d_statinfo.d_ino, G)) {
    return 1; // F was within D
  } else if (0 == strcmp("/", G) {
    return 0; // F was not within D.
  }
  G = dirname(G);
}

samefile函数很简单:

int samefile(dev_t ddev, ino_t dino, const char *path) {
  struct stat st;
  if (0 == stat(path, &st)) {
    return ddev == st.st_dev && dino == st.st_no;
  } else {
    throw ...; // or return error value (but also change the caller to detect it)
  }
}

这适用于POSIX文件系统。但是许多文件系统都不是POSIX。需要注意的问题包括:

  1. 设备/ inode不唯一的文件系统。一些FUSE文件系统就是这样的例子;当底层文件系统没有它们时,它们有时会组成inode编号。它们不应该重用inode号,但有些FUSE文件系统有bug。
  2. NFS实施损坏。在某些系统上,所有NFS文件系统都具有相同的设备编号。如果它们通过服务器上存在的inode编号,这可能会导致问题(虽然我从未在实践中看到过这种情况)。
  3. Linux绑定挂载点。如果/a/b的绑定挂载,那么/a/1正确显示在/a内,但是通过上面的实现,/b/1似乎也在/a。我认为这可能是正确的答案。但是,如果这不是您喜欢的结果,可以通过更改return 1案例来调用strcmp()来比较路径名称,从而轻松解决此问题。但是,要实现此功能,您需要首先在F和D上调用realpathrealpath调用可能非常昂贵(因为它可能需要多次点击磁盘)。
  4. 特殊路径//foo/bar。 POSIX允许以//开头的路径名称在某种程度上没有明确定义的情况下是特殊的。实际上我忘记了POSIX提供的语义保证的精确级别。我认为POSIX允许//foo/bar//baz/ugh引用同一个文件。设备/ inode检查仍应为您做正确的事情,但您可能发现它没有(即您可能会发现//foo/bar//baz/ugh可以引用相同的文件但具有不同的设备/ inode编号)。
  5. 这个答案假定我们从F和D的绝对路径开始。如果不能保证这一点,您可能需要使用realpath()getcwd()进行一些转换。如果当前目录的名称长于PATH_MAX(这肯定会发生),这将是一个问题。

答案 2 :(得分:0)

您应该自己处理..并删除之前的路径组件,以便在用于打开文件的最终字符串中不会出现..