You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Guard against negative offset/length values in tarfile's GNU sparse extraction #137396
Actually, I forgot about it but I think there is a similar check we now do: 7040aa5. I didn't check if this validates the structs, though so maybe extra validation is needed here (more generally, I'm not sure whether we should really stop if we encounter a ValueError rather than raising indicating a bad GNU sparse header).
cc @gpshead (maybe we should recategorize it as a security issue)
@gpshead When I raise an error if n < 0 in nti() I see a single test failure in the test_read_number_fields, but looking at the issue where the possibility of negative integers was added there's an associated tar file. I downloaded and tried extracting this tar file and even this tar file extracts when disallowing negative numbers to result from nti() as the archive is simply using base-256 number encoding format but not to encode a negative integer.
I did a bunch of digging around tar standards and didn't see any mentions about disallowing negative numbers wholesale, but I think it definitely makes sense to disallow negative numbers in any framing values (size, offset).
The sp is an array of struct sparse. Each struct sparse contains two 12-character strings which represent an offset into the file and a number of bytes to be written at that offset. The offset is absolute, and not relative to the offset in preceding array element.
Negative seek is forbidden, (it should raise). Even if copyfileobj() doesn't check size < 0, we have:
blocks, remainder=divmod(length, bufsize)
forbinrange(blocks):
buf=src.read(bufsize)
iflen(buf) <bufsize:
raiseexception("unexpected end of data")
dst.write(buf)
ifremainder!=0:
buf=src.read(remainder)
iflen(buf) <remainder:
raiseexception("unexpected end of data")
dst.write(buf)
If length is negative, blocks is also negative, so we would skip the first loop.
where the possibility of negative integers was added there's an associated tar file
The specs allow for negative numbers but those may not necessarily be used in the sparse header data. I actually added some print() in the nti() function where negative values are handled, but the path doesn't seem to be hit (there is some hit when the converted value is 0, so this could be the bad case).
We could categorize it for now as a non-security issue (sorry for having tagged it, but as I was travelling I wasn't able to check this: I assumed that the offset could be relative and possibly have cycles when parsing but that doesn't appear to be the case).
If however, a corrupted extraction is considered a security issue (well it could, but apart from skipping some stuff I don't know how it could be exploited), we could keep the security label.
Is there ever any valid reason for any .tar file integer field to be negative?
The specs allow it, though I don't know in which case we would have it:
For fields containing numbers or timestamps that are out of range for the basic
format, the GNU format uses a base-256 representation [...] If the leading byte is
0xff (255), all the bytes of the field (including the leading byte) are concatenated
in big-endian order, with the result being a negative number expressed in two’s
complement form
Yes, it can. At least I don't see a way to exploit it. Sorry for the false alarm, I wrongly assumed that it could be the same issue as the other one that was tagged as a security issue.
Now, about how to fix this, I suggest the following:
Add a parameter to number dumping/loading functions to allow or reject negative integers. As per tar specs, some values are allowed to be negative, while others are not.
Fortunately, seek() forbids negative offsets so Python should raise it it encounters a negative offset (IOW, it's possible to parse a negative offset but it won't be usable as is). Similarly, divmod preserves the sign of the dividend so if the size is negative, we have a positive remainder with either a partial content or an incomplete read.
Raise correct errors. I suggest raising InvalidHeader (I don't remember the exact name of the exception) instead of ValueError when encountering negative framing sizes.
I think we should still backport the change to 3.9 because not all values could be passed to the _block function which has been modified in #137027. Instead, we should carefully decide which values can be negative and which cannot and raise appropriately (though _block() is only called with the .size value).
Activity
picnixz commentedon Aug 7, 2025
Actually, I forgot about it but I think there is a similar check we now do: 7040aa5. I didn't check if this validates the structs, though so maybe extra validation is needed here (more generally, I'm not sure whether we should really stop if we encounter a ValueError rather than raising indicating a bad GNU sparse header).
cc @gpshead (maybe we should recategorize it as a security issue)
VbhvGupta commentedon Aug 7, 2025
Thank you for analysis @picnixz
I agree that while the check in 7040aa5 is related, the explicit validation for negative values still seems necessary to be completely safe.
I agree, raising an exception to indicate a bad header would be much more robust than the current behavior.
Thank you for suggesting it be recategorized as a security issue. That was my primary concern, and I appreciate you escalating it.
moreal commentedon Aug 15, 2025
@corona10 Could you assign me on this issue?
pythongh-137396: Raise InvalidHeaderError when offset or numbytes is …
pythongh-137396: Raise InvalidHeaderError when offset or numbytes is …
pythongh-137396: Raise InvalidHeaderError when offset or numbytes is …
gpshead commentedon Aug 17, 2025
Is there ever any valid reason for any .tar file integer field to be negative?
sethmlarson commentedon Aug 18, 2025
@gpshead When I raise an error if
n < 0innti()I see a single test failure in thetest_read_number_fields, but looking at the issue where the possibility of negative integers was added there's an associated tar file. I downloaded and tried extracting this tar file and even this tar file extracts when disallowing negative numbers to result fromnti()as the archive is simply using base-256 number encoding format but not to encode a negative integer.I did a bunch of digging around tar standards and didn't see any mentions about disallowing negative numbers wholesale, but I think it definitely makes sense to disallow negative numbers in any framing values (size, offset).
picnixz commentedon Aug 18, 2025
The GNU specs say:
AFAIK, the structs are used only in
makefile:Negative seek is forbidden, (it should raise). Even if copyfileobj() doesn't check
size < 0, we have:If length is negative, blocks is also negative, so we would skip the first loop.
The specs allow for negative numbers but those may not necessarily be used in the sparse header data. I actually added some print() in the nti() function where negative values are handled, but the path doesn't seem to be hit (there is some hit when the converted value is 0, so this could be the bad case).
We could categorize it for now as a non-security issue (sorry for having tagged it, but as I was travelling I wasn't able to check this: I assumed that the offset could be relative and possibly have cycles when parsing but that doesn't appear to be the case).
If however, a corrupted extraction is considered a security issue (well it could, but apart from skipping some stuff I don't know how it could be exploited), we could keep the security label.
picnixz commentedon Aug 18, 2025
The specs allow it, though I don't know in which case we would have it:
sethmlarson commentedon Aug 25, 2025
@picnixz If there's no security risk for negative values in this case we can handle this as a non-security issue and the tag can be removed.
picnixz commentedon Aug 25, 2025
Yes, it can. At least I don't see a way to exploit it. Sorry for the false alarm, I wrongly assumed that it could be the same issue as the other one that was tagged as a security issue.
gpshead commentedon Aug 25, 2025
it was worth a look, thanks for the investigations!
picnixz commentedon Aug 28, 2025
Now, about how to fix this, I suggest the following:
seek()forbids negative offsets so Python should raise it it encounters a negative offset (IOW, it's possible to parse a negative offset but it won't be usable as is). Similarly, divmod preserves the sign of the dividend so if the size is negative, we have a positive remainder with either a partial content or an incomplete read.I think we should still backport the change to 3.9 because not all values could be passed to the
_blockfunction which has been modified in #137027. Instead, we should carefully decide which values can be negative and which cannot and raise appropriately (though_block()is only called with the.sizevalue).pythongh-137396: Guard against negative offset in tarfile (GH-XXXXX)