2011-08-20 123 views
6

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

例如,Web服务器的根目录为/srv,这是守护进程的getcwd()。 解析/index.html的请求时,将返回/srv/index.html的内容。

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

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

分裂在/路径和拒绝含有..将打破任何有效数组访问/srv/valid/../index.html

有没有一个规范的方式来做到这一点与系统调用?还是我需要手动走路径和计算目录深度?

+2

我认为这是'chroot(2)'发明的原因! –

+0

@Carl Norum:如果你给某人有限的shell权限,chroot会更好。如果你不想限制你创建的程序的访问权限,那么比chroot有更好的选择。 – Dani

回答

6

总是有realpath

真实路径()功能要计算,从路径由* FILE_NAME *,绝对路径解析到同一个目录项,其分辨率不涉及指出, '' ,'..'或符号链接。

然后比较realpath为您提供了所需的根目录并查看它们是否匹配。

您也可以在预先设定"/srv"之前通过展开双点来清理文件名。在斜线上分割输入路径,并逐一穿过它。如果你得到一个"."然后删除它,继续前进;如果你得到一个"..",然后删除它和以前的组件(注意不要超过列表中的第一个条目);如果您还有其他任何东西,请继续阅读下一个组件。然后在组件之间粘贴剩余的斜线,并在前面加上"/srv/"。所以,如果有人给你"/valid/../../etc/passwd",你最终将得到"/srv/etc/passwd""/where/is/../pancakes/house"将最终为"/srv/where/pancakes/house"

这样,你不能让外面"/srv"(除了通过当然是符号链接)和传入"/../.."将是相同的"/"(就像一个普通的文件系统)。但是如果您担心"/srv"下的符号,您仍然希望使用realpath

通过组件处理路径名组件还可以让您断开您向外界展示的布局与实际文件系统布局之间的连接; "/this/that/other/thing"不需要映射到任何地方的实际"/srv/this/that/other/thing"文件,路径可能只是某种数据库中的键或某种函数调用的命名空间路径。

0

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

2

要确定文件F是否位于目录D中,首先需要确定其设备号和inode编号(struct stat的st_dev和st_ino成员)的属性D.

然后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文件系统有错误。
  2. 破坏的NFS实现。在某些系统上,所有的NFS文件系统都有相同的设备编号。如果它们通过服务器上存在的inode号码,这可能会导致问题(尽管我从来没有在实践中看到它发生过)。
  3. Linux绑定挂载点。如果/a/b的绑定挂载,则/a/1正确地看起来在/a内,但是对于上面的实现,/b/1也似乎在/a内。我认为这可能是正确的答案。但是,如果这不是您喜欢的结果,则可以通过更改return 1大小写以调用strcmp()来比较路径名,从而轻松修复此问题。然而,为了这个工作,您需要先在F和D上拨打realpath开始。realpath调用可能非常昂贵(因为它可能需要多次点击磁盘)。
  4. 特殊路径//foo/bar。 POSIX允许以//开头的路径名称是特殊的,这种方式有点不明确。实际上,我忘记了POSIX提供的关于语义的确切级别的保证。我认为POSIX允许//foo/bar//baz/ugh引用同一个文件。设备/ inode检查应该仍然适合您,但您可能会发现它不会(也就是说,您可能会发现//foo/bar//baz/ugh可以引用相同的文件,但具有不同的设备/ inode编号)。

这个回答假设我们开始与F和D.如果不能保证这一点,你可能需要做使用realpath()getcwd()一些转换的绝对路径。如果当前目录的名称长于PATH_MAX(这肯定会发生),这将会成为问题。