Skip to content
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

Merged

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented Jun 26, 2018

  • 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

* 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.
Copy link
Member

@ned-deily ned-deily left a 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.
Copy link
Member

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.
Copy link
Member

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.

@bedevere-bot
Copy link

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

sbytes += i;
for (i = 0; i < sf.hdr_cnt; i++) {
Py_ssize_t blen = sf.headers[i].iov_len;
# define OFF_T_MAX 0x7fffffffffffffff
Copy link
Member Author

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

@ned-deily
Copy link
Member

Thanks for the changes, @serhiy-storchaka. @ronaldoussoren Do you have time to review this?

Copy link
Contributor

@ronaldoussoren ronaldoussoren left a 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
Copy link
Contributor

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.

@serhiy-storchaka
Copy link
Member Author

@ronaldoussoren, how to move this issue forward?

@ronaldoussoren
Copy link
Contributor

@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.

@serhiy-storchaka
Copy link
Member Author

Thanks!

@serhiy-storchaka serhiy-storchaka merged commit 9d57273 into python:master Jul 31, 2018
@miss-islington
Copy link
Contributor

Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 2.7, 3.6, 3.7.
🐍🍒⛏🤖

@serhiy-storchaka serhiy-storchaka deleted the sendfile-macos-overflow branch July 31, 2018 07:25
@bedevere-bot
Copy link

GH-8583 is a backport of this pull request to the 3.7 branch.

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 31, 2018
…-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>
@miss-islington
Copy link
Contributor

Sorry, @serhiy-storchaka, I could not cleanly backport this to 3.6 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker 9d5727326af53ddd91016d98e16ae7cf829caa95 3.6

@miss-islington
Copy link
Contributor

Sorry, @serhiy-storchaka, I could not cleanly backport this to 2.7 due to a conflict.
Please backport using cherry_picker on command line.
cherry_picker 9d5727326af53ddd91016d98e16ae7cf829caa95 2.7

serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request Jul 31, 2018
…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>
@bedevere-bot
Copy link

GH-8584 is a backport of this pull request to the 3.6 branch.

miss-islington added a commit that referenced this pull request Jul 31, 2018
* 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>
serhiy-storchaka added a commit that referenced this pull request Jul 31, 2018
…-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)
@serhiy-storchaka serhiy-storchaka removed their assignment Sep 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type-bug An unexpected behavior, bug, or error type-security A security issue
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants