On Mon, Feb 19, 2018 at 9:07 PM, Denys Vlasenko
<vda.li...@googlemail.com> wrote:
> On Mon, Feb 19, 2018 at 8:52 PM, Harald van Dijk <har...@gigawatt.nl> wrote:
>> On 19/02/2018 17:15, Denys Vlasenko wrote:
>>> On Mon, Feb 19, 2018 at 5:09 PM, Harald van Dijk <har...@gigawatt.nl>
>>>>>>> Let's also brainstorm option 3:
>>>>>>> Allow symlinks which
>>>>>>> (a) start with one or more "../";
>>>>>>> (b) never end up on a higher level of directory tree:
>>>>>>> "../dir/.." is ok,
>>>>>>> "../../usr/bin/.." is ok,
>>>>>>> "../file" is not ok,
>>>>>>> "../../dir/file" is not ok;
>>>>>>> and (c) they also should not leave tarred tree:
>>>>>>> symlink "sbin/mkswap" to "../bin/busybox" is ok, but
>>>>>>> symlink "mkswap" to "../bin/busybox" is not ok (it hops "above" the
>>>>>>> tree).
>>>>>>>
>>>>>>> This sounds a bit complicated, but it can be achieved just by looking
>>>>>>> at names, no multiple lstat() calls for every open() needed.
>>>>>>>
>>>>>>> With only these symlinks allowed, we know that if we untar to an empty
>>>>>>> directory or to a formerly empty directory with another tarball
>>>>>>> untarred, any pathname we open, whilst it can contain components which
>>>>>>> are symlinks, they nevertheless can not allow new opens to "leave" the
>>>>>>> tree and create files elsewhere.
>>>>>>
>>>>>>
>>>>>> This wouldn't be safe, I think. Consider a tarball containing
>>>>>>
>>>>>>     a/
>>>>>>     b/
>>>>>>     a/a -> ../b
>>>>>>     a/a/a -> ../../c
>>>>>
>>>>>
>>>>>
>>>>> The link to "../../c" is not allowed - it fails criteria (b).
>>>>
>>>>
>>>> You wrote "it can be achieved just by looking at names", but that's not
>>>> enough here: you have to know that a/a/a is actually b/a, so only one
>>>> level
>>>> deep in the -C directory, to know that ../../c points outside the -C
>>>> directory.
>>>
>>>
>>> Yes... but:
>>>
>>>> Since the a/a -> ../b symlink may not even be in this archive,
>>>> the only way to determine that is by lstat().
>>>
>>>
>>> I assume that tarball(s) are being unpacked into an empty directory.
>>
>>
>> Yes. To be explicit: the case of unpacking a single tarball into an empty
>> directory was already handled by your earlier
>> <https://git.busybox.net/busybox/commit/?id=b920a38dc0a87f5884444d4731a8b887b5e16018>.
>> The point of
>> <https://git.busybox.net/busybox/commit/?id=bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7>
>> was to secure unpacking multiple tarballs into the same previously empty
>> directory, so I assume that a tarball is being unpacked into a directory in
>> which an earlier invocation of tar had potentially already created files,
>> but no other tool.
>>
>>> The case when someone already placed malicious symlinks there
>>> before unpacking would be a bug in whatever tool allowed _that_
>>> to happen.
>>
>>>
>>>
>>> My goal here is to not allow tar (and unzip) to create such symlinks.
>>
>>
>> a/a -> ../b is not a malicious symlink, because assuming a is a directory,
>> a/a will not point outside the -C directory. That's all you can determine
>> from looking at the names. This symlink would be allowed by your
>> unsafe_symlink_target function.
>>
>> a/a/a -> ../../c is not a malicious symlink by itself, because assuming a
>> and a/a are directories, it does not point outside the -C directory.
>
> It is malicious and will not be allowed by the code I propose:
> it has two ".." leading components but only one subsequent component.
> Specifically, this part of code will reject it:
>
> +               /* Now skip the same number of non-dot[dot]
> +                * components in link target as it had leading ".." components
> +                * - or bail out with error.
> +                */
> +               do {
> +                       unsigned len = strchrnul(target, '/') - target;
> +                       if (len == 0) {
> +                               /* Examples: "../", "../../abc", "../../abc/" 
> */
> +                               return 1; /* unsafe */
> +                       }
> +                       if (len <= 2
> +                        && (len < 2 || target[1] == '.')
> +                        && target[0] == '.'
> +                       ) {
> +                               /* "." or ".." */
> +                               return 1; /* unsafe */
> +                       }
> +                       target += len;
> +                       while (*target == '/') target++;
> +                       up_count--;
> +               } while (up_count != 0);
>
>> That's
>> again all you can determine from looking at the names. Based on your
>> explanation here, I had thought that you wanted this to be accepted, but
>> reading your unsafe_symlink_target function, *this* is the symlink which
>> would not be accepted.
>
> Exactly.
>
>> However, consider this different example instead:
>>
>>   cur -> .
>>   cur/dir -> ../dir
>>   cur/dir/file
>>
>> These two symlinks are accepted and there is no indication, just looking at
>> each in isolation, that anything is wrong. Still, the end result is that
>> ../dir/file would be created, outside of the -C directory.
>
> Indeed :(


commit a84db18fc71d09e801df0ebca048d82e90b32c6a
Author: Denys Vlasenko <vda.li...@googlemail.com>
Date:   Tue Feb 20 15:57:45 2018 +0100

    tar,unzip: postpone creation of symlinks with "suspicious" targets

    This mostly reverts commit bc9bbeb2b81001e8731cd2ae501c8fccc8d87cc7
    "libarchive: do not extract unsafe symlinks unless
$EXTRACT_UNSAFE_SYMLINKS=1"

    Users report that it is somewhat too restrictive. See
    https://bugs.busybox.net/show_bug.cgi?id=8411

    In particular, this interferes with unpacking of busybox-based
    filesystems with links like "sbin/applet" -> "../bin/busybox".

    The change is made smaller by deleting ARCHIVE_EXTRACT_QUIET flag -
    it is unused since 2010, and removing conditionals on it
    allows commonalizing some error message code.
_______________________________________________
busybox mailing list
busybox@busybox.net
http://lists.busybox.net/mailman/listinfo/busybox

Reply via email to