How to determine if a path is inside a directory? (POSIX)

In C, using POSIX calls, how to determine if the path is in the destination directory?

For example, the web server has its root directory in /srv , this is getcwd() for the daemon. When parsing a request for /index.html it returns the contents of /srv/index.html .

How can I filter requests for paths outside of /srv ?

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

Separating the path to / and rejecting any array containing .. will break the valid calls /srv/valid/../index.html .

Is there a canonical way to do this using system calls? Or do I need to manually go through the path and calculate the depth of the directory?

+6
source share
3 answers

Always realpath :

The realpath () function should infer from the pathname pointed to by * filename * the absolute path that resolves the same entry in the directory, the resolution of which is not associated with "."., '..' or symbolic links.

Then compare what realpath will provide you with the desired root directory and see if they match.

You can also clear the file name manually by expanding the double dots before you add "/srv" . Divide the incoming path into slashes and go through it in parts. If you get a "." then delete it and move it; if you get ".." , then delete it and the previous component (without worrying about the first entry in your list); if you get something else, just skip to the next component. Then insert what's left with a slash between the components and add "/srv/" . Therefore, if someone gives you "/valid/../../etc/passwd" , you will get "/srv/etc/passwd" and "/where/is/../pancakes/house" in the end "/srv/where/pancakes/house" .

Thus, you cannot go beyond "/srv" (with the exception of conditional links, of course), and the incoming "/../.." will be the same as "/" (as in a regular file system). But you still want to use realpath if you are worried about the symbols in "/srv" .

Working with the component the path name of the component will also allow you to break the connection between the layout that you present in the outside world and the actual location of the file system; there is no need for "/this/that/other/thing" to map the actual file "/srv/this/that/other/thing" anywhere, the path may just be a key in some database or some kind of space path names to the function call.

+6
source

To determine if file F is in directory D, first stat D determine its device number and inode number (members st_dev and st_ino struct stat).

Then stat F to determine if it is a directory. If not, call basename to determine the name of the directory containing it. Set G to the name of this directory. If F was already a directory, then set G = F.

Now F is inside D if and only if G is inside D. Then we have a loop.

 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); } 

A similar file function is simple:

 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) } } 

This will work with POSIX file systems. But many file systems are not POSIX. Issues to consider include:

  • File systems in which a device / index is not unique. Examples are some FUSE file systems; they sometimes compose index numbers if their underlying file systems do not. They should not reuse inode numbers, but some FUSE file systems have errors.
  • Broken NFS implementations. On some systems, all NFS file systems have the same device number. If they pass through the inode number as it exists on the server, this can cause a problem (although I have never seen this happen in practice).
  • Linux binds mount points. If /a is a binding of the /b binding, then /a/1 correctly displayed inside /a , but with the implementation above /b/1 also appears inside /a . I think that is probably the correct answer. However, if this is not the result you prefer, it can be easily strcmp() changing the case of return 1 to call strcmp() to compare the path names. However, for this you will need to start by calling realpath on both F and D. Calling realpath can be quite expensive (since you may need to press the drive several times).
  • Special path //foo/bar . POSIX allows you to specify path names starting with // in a specific way that is somewhat undefined. In fact, I forget the exact level of guarantee about the semantics provided by POSIX. I think POSIX allows //foo/bar and //baz/ugh to reference the same file. The device / inode check should still do the right thing, but you may find that it is not (for example, you may find that //foo/bar and //baz/ugh may refer to the same file, but have different device / index numbers).

This answer assumes that we start with an absolute path for both F and D. If this is not guaranteed, you may need to do some conversion using realpath() and getcwd() . This will be a problem if the name of the current directory is larger than PATH_MAX (which certainly could happen).

+2
source

You should simply process .. yourself and remove the previous component of the path when it is detected, so that there is no entry in the last line used to open the files ..

0
source

Source: https://habr.com/ru/post/895505/


All Articles