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
bpo-33871: Fix os.sendfile(), os.writev(), os.readv(), etc. #7931
bpo-33871: Fix os.sendfile(), os.writev(), os.readv(), etc. #7931
Conversation
* Fix integer overflow in os.readv(), os.writev(), os.preadv() and os.pwritev() and in os.sendfile() with headers or trailers arguments (on BSD-based OSes and MacOS). * Fix sending the part of the file in os.sendfile() on MacOS. Using the trailers argument could cause sending more bytes from the input file than was specified. Thanks Ned Deily for testing on 32-bit MacOS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not review the code other than the NEWS entries, someone else more familiar with sendfile should. The tests seem to run without error on 32-bit macOS.
Fixed integer overflow in :func:`os.readv`, :func:`os.writev`, | ||
:func:`os.preadv` and :func:`os.pwritev` and in :func:`os.sendfile` with | ||
*headers* or *trailers* arguments (on BSD-based OSes and MacOS). | ||
Thanks Ned Deily for testing on 32-bit MacOS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MacOS -> macOS. Also, thanks for the "thanks" but that is not needed for a NEWS entry.
@@ -0,0 +1,3 @@ | |||
Fixed sending the part of the file in :func:`os.sendfile` on MacOS. Using | |||
the *trailers* argument could cause sending more bytes from the input file | |||
than was specified. Thanks Ned Deily for testing on 32-bit MacOS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same two comments as above.
When you're done making the requested changes, leave the comment: |
sbytes += i; | ||
for (i = 0; i < sf.hdr_cnt; i++) { | ||
Py_ssize_t blen = sf.headers[i].iov_len; | ||
# define OFF_T_MAX 0x7fffffffffffffff |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this. Is off_t
always 64-bit on macOS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to pymacconfig.h
, off_t
is equal to long long
on 32-bit, and long
on 64-bit. E.g. it is always 64-bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've checked the SDK headers to be sure, and off_t is indeed always 64-bit (off_t is a typedef for __darwin_off_t, and that in turn is a typedef for __int64_t).
The manpage for sendfile(2) doesn't mention this, but the one for writev(2) says:
[EINVAL] The sum of the iov_len values in the iov array overflows a 32-bit integer.
Furthermore we have had problems with write(2) in the past when writing more than INT_MAX bytes at a time (see the implementation of _Py_write_impl). I think it is therefore better to limit the maximum size to INT_MAX, unless we explicitly test that sendfile can send more than INT_MAX bytes in one go.
BTW. Instead of OFF_T_MAX you could use OFF_MAX here, that should be defined to the maximum value of off_t.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is OFF_MAX a macOS specific constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have not just the sum of the iov_len values, but also added the length of the data sent from a file. The total sum can exceed 32-bit limit even if the sum of the iov_len values is in 32-bit limit.
I think it is better to use a 64-bit limit and allow OS to return an error if this is not supported. We should guard only from integer overflows occurred on the border between Python and OS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In _PyWrite_impl we explicitly guard against overflowing a 32-bit integer to avoid problems. IIRC this was done because this gives a nicer user experience (and hoping that Apple will fix this in the kernel is not really useful).
I'll see if I can create a simple test to check if sendfile suffers from the same limitation on 10.13 and 10.14.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In _PyWrite_impl there is a different situation. We guard against overflowing int
on Windows because write()
accepts only unsigned int
as the count argument on Windows, and because it is allowed to write less bytes then requested in write()
. This is a border between Python and OS.
readv()
, writev()
and sendfile()
accept size_t
as the length of every chunk on all supported OSes even if OS doesn't support writing too large total size. I'm not sure that it is better to silently truncate the output than raise OSError. At least sendfile()
with headers and trailers will produce corrupted output in this case: headers, than truncated content, than trailers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_Py_write_impl does indeed not limit writes, I was confused because I have a local patch for issue 24658 that I never pushed...
sendfile like write can write less bytes than requested (at least, that's what the API suggests as it returns the amount of bytes written), limiting the amount of data written to avoid misbehaviour of the OS shouldn't be a problem.
That said: limiting is only necessary when there is a reason to do so. I guess I'll really have to write a test program that checks what the system does when trying to write more than 2GB of data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please test sending 2 GiB with headers and trailers.
os.sendfile(sockno, fileno, 0, 2**31-1, headers=[b"[begin]"], trailers=[b"[end]"])
If truncate the total size to INT_MAX, then this can send b"[begin]"
, then 2**31-1-7
bytes from the file (instead of expected 2**31-1
bytes) and then b"[end]"
. This is a wrong result.
Thanks for the changes, @serhiy-storchaka. @ronaldoussoren Do you have time to review this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than the comment about worries w.r.t. the actual limit below (OFF_MAX vs. INT_MAX) the patch looks good.
sbytes += i; | ||
for (i = 0; i < sf.hdr_cnt; i++) { | ||
Py_ssize_t blen = sf.headers[i].iov_len; | ||
# define OFF_T_MAX 0x7fffffffffffffff |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've checked the SDK headers to be sure, and off_t is indeed always 64-bit (off_t is a typedef for __darwin_off_t, and that in turn is a typedef for __int64_t).
The manpage for sendfile(2) doesn't mention this, but the one for writev(2) says:
[EINVAL] The sum of the iov_len values in the iov array overflows a 32-bit integer.
Furthermore we have had problems with write(2) in the past when writing more than INT_MAX bytes at a time (see the implementation of _Py_write_impl). I think it is therefore better to limit the maximum size to INT_MAX, unless we explicitly test that sendfile can send more than INT_MAX bytes in one go.
BTW. Instead of OFF_T_MAX you could use OFF_MAX here, that should be defined to the maximum value of off_t.
@ronaldoussoren, how to move this issue forward? |
@serhiy-storchaka: I propose merging as is, and creating a followup patch if it turns out that my size concerns are valid. The reason I propose this is that the basic patch looks fine and I don't know how soon I'll have time to write (C) code to test the behaviour of the sendfile syscall w.r.t. buffer sizes. |
Thanks! |
Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 2.7, 3.6, 3.7. |
GH-8583 is a backport of this pull request to the 3.7 branch. |
…-7931) * Fix integer overflow in os.readv(), os.writev(), os.preadv() and os.pwritev() and in os.sendfile() with headers or trailers arguments (on BSD-based OSes and MacOS). * Fix sending the part of the file in os.sendfile() on MacOS. Using the trailers argument could cause sending more bytes from the input file than was specified. Thanks Ned Deily for testing on 32-bit MacOS. (cherry picked from commit 9d57273) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
…thonGH-7931) * Fix integer overflow in os.readv(), os.writev(), os.preadv() and os.pwritev() and in os.sendfile() with headers or trailers arguments (on BSD-based OSes and MacOS). * Fix sending the part of the file in os.sendfile() on MacOS. Using the trailers argument could cause sending more bytes from the input file than was specified. Thanks Ned Deily for testing on 32-bit MacOS.. (cherry picked from commit 9d57273) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
GH-8584 is a backport of this pull request to the 3.6 branch. |
* Fix integer overflow in os.readv(), os.writev(), os.preadv() and os.pwritev() and in os.sendfile() with headers or trailers arguments (on BSD-based OSes and MacOS). * Fix sending the part of the file in os.sendfile() on MacOS. Using the trailers argument could cause sending more bytes from the input file than was specified. Thanks Ned Deily for testing on 32-bit MacOS. (cherry picked from commit 9d57273) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
…-7931) (GH-8584) * Fix integer overflow in os.readv(), os.writev() and in os.sendfile() with headers or trailers arguments (on BSD-based OSes and MacOS). * Fix sending the part of the file in os.sendfile() on MacOS. Using the trailers argument could cause sending more bytes from the input file than was specified. Thanks Ned Deily for testing on 32-bit MacOS. (cherry picked from commit 9d57273)
Fix integer overflow in os.readv(), os.writev(), os.preadv()
and os.pwritev() and in os.sendfile() with headers or trailers
arguments (on BSD-based OSes and MacOS).
Fix sending the part of the file in os.sendfile() on MacOS.
Using the trailers argument could cause sending more bytes from
the input file than was specified.
Thanks Ned Deily for testing on 32-bit MacOS.
https://bugs.python.org/issue33871