Skip to content

bpo-36161: Use thread-safe function ttyname_r instead of unsafe one ttyname #14868

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

Merged
merged 2 commits into from
Oct 9, 2019

Conversation

chibby0ne
Copy link
Contributor

@chibby0ne chibby0ne commented Jul 20, 2019

Signed-off-by: Antonio Gutierrez chibby0ne@gmail.com

https://bugs.python.org/issue36161

@chibby0ne
Copy link
Contributor Author

Hi all, I'm a new contributor and I've made a PR taking into account the discussion seen in the issue page.

For the ttyname_r(), I've used the buffer length of MAXPATHLEN, since I saw it used for several functions inside the same file, like: os.readlink, os.getcw, among others.

For the crypt_r() side, I've added a few unit tests one that raises the error and another that doesn't but could add more if needed.

This is how a raised error would look when passing an invalid salt:

>>> crypt.crypt('password', '__', raise_error=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/cpython/Lib/crypt.py", line 73, in crypt
    return _crypt.crypt(word, salt, raise_error)
OSError: [Errno 22] Invalid argument

@chibby0ne
Copy link
Contributor Author

I don't understand the error in the MacOS Builder:

ERROR: test_invalid_salt_raises_error_false (test.test_crypt.CryptTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/vsts/agent/2.154.3/work/1/s/Lib/test/test_crypt.py", line 90, in test_invalid_salt_raises_error_false
    res = crypt.crypt('password', '__')
  File "/Users/vsts/agent/2.154.3/work/1/s/Lib/crypt.py", line 73, in crypt
    return _crypt.crypt(word, salt, raise_error)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xfd in position 3: invalid start byte

I don't understand since they are both unicode, and password is clearly utf-8 and the second argument are two _ underscore chars. Also the 0xfd byte is 'ý'.

If it were some codec issue wouldn't it fail in other OSes?

Likewise for the other added test.

Copy link
Contributor

@mangrisano mangrisano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi and thank you for this PR and for providing tests.

LGTM.

@chibby0ne
Copy link
Contributor Author

@mangrisano Thanks for the approval. Unfortunately the CI is failing in MacOS. If you can, would you give me any pointers as to the cause of the issue? I truly don't understand it 😞

@chibby0ne
Copy link
Contributor Author

Hi @mangrisano, and any others. Just a friendly ping to mention that this is ready to be merged.

@chibby0ne
Copy link
Contributor Author

Hi all,
I need some help understanding why the MacOS test fails with that unicode error, specially since it passes for every other OS. I don't have access to a MacOS environment and have no way of testing this locally.
image

Copy link
Contributor

@benjaminp benjaminp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand these raise_error changes. Those don't seem related to changing the underlying library call.

@@ -2712,13 +2712,14 @@ static PyObject *
os_ttyname_impl(PyObject *module, int fd)
/*[clinic end generated code: output=c424d2e9d1cd636a input=9ff5a58b08115c55]*/
{
char *ret;
int ret;
char buffer[MAXPATHLEN];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the right buffer size is actually TTY_NAME_MAX

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. This macro wasn't mentioned in the version of the man pages of my local dev machine (i.e: man ttyname) but after googling this constant, it is indeed mentioned in man limits.h which is imported by Python.h in the base code.
Good catch! Thanks for the recommendation!


ret = ttyname(fd);
if (ret == NULL) {
ret = ttyname_r(fd, buffer, MAXPATHLEN);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer sizeof(buffer)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@chibby0ne
Copy link
Contributor Author

I don't understand these raise_error changes. Those don't seem related to changing the underlying library call.

Well according to the discussion in: https://bugs.python.org/issue36161, due to the fact that we are now returning the error code set by crypt_r (if any), this is a breaking change, so in order to maintain backwards compatible behavior (where regarless of any error it would return None), we add this boolean keyword argument raise_error.
But ofc I'm open to any suggestions 😄

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@benjaminp: please review the changes made to this pull request.

@benjaminp
Copy link
Contributor

I don't understand these raise_error changes. Those don't seem related to changing the underlying library call.

Well according to the discussion in: https://bugs.python.org/issue36161, due to the fact that we are now returning the error code set by crypt_r (if any), this is a breaking change, so in order to maintain backwards compatible behavior (where regarless of any error it would return None), we add this boolean keyword argument raise_error.

While I agree it's technically a breaking change, I think not raising errors from a failed cryptographic function is severely buggy behavior. "Errors should never pass silently."

But ofc I'm open to any suggestions smile

I think errors should always be propagated from crypt or you should follow the suggestion of https://bugs.python.org/issue36161#msg351981 and simply drop the crypt changes.

@chibby0ne
Copy link
Contributor Author

While I agree it's technically a breaking change, I think not raising errors from a failed cryptographic function is severely buggy behavior. "Errors should never pass silently."

Wise aphorism! I agree wholeheartedly.

I think errors should always be propagated from crypt or you should follow the suggestion of https://bugs.python.org/issue36161#msg351981 and simply drop the crypt changes.

Yeah it looks like this will be eventually deprecated and removed. I've decided to simply raise the error in accordingly, since it helps the user but also we are not over engineering future deprecated code.

@chibby0ne
Copy link
Contributor Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@benjaminp: please review the changes made to this pull request.

Copy link
Contributor

@benjaminp benjaminp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, but now the tests are failng

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.


ret = ttyname(fd);
if (ret == NULL) {
#ifndef TTY_NAME_MAX /* MacOS doesn't define this macro */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, maybe it's better to use sysconf(_SC_TTY_NAME_MAX) for portability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure about this because I saw the values were quite different between the ones used in OpenBSD and the ones used in Linux, but I agree is the more portable solution

Lib/crypt.py Outdated
result = None
try:
result = crypt('', salt)
except OSError:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check that the errno of this error is EINVAL?

I think it would also be good to ensure that at least one method works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking! Done! Thanks a lot 👍

@@ -0,0 +1 @@
Use thread-safe functions instead of unsafe ones (crypt, ttyname)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Use ttyname_r instead of ttyname and crypt_r instead of crypt for thread safety."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to update this, because it was autogenerated from the first commit. Thanks a lot for your review 😉

@bedevere-bot bedevere-bot requested a review from benjaminp October 4, 2019 18:34
Copy link
Contributor

@benjaminp benjaminp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are really two logical changes in this PR: checking the error of crypt and using ttyname_r. It would make sense to split them into separate PRs I think.

return posix_error();
}
return PyUnicode_DecodeFSDefault(ret);
char *buffer = (char *) PyMem_RawMalloc(size);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: no space between the cast and the function call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

int ret = ttyname_r(fd, buffer, size);
if (ret != 0) {
errno = ret;
return posix_error();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch now leaks buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Thanks ;)

Comment on lines 2791 to 2792
PyObject *res;
res = PyUnicode_DecodeFSDefault(buffer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fold these two lines into one:

PyObject *res = PyUnicode_DecodeFSDefault(buffer);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@@ -0,0 +1 @@
For thread safetyy, use ``ttyname_r(3)`` instead of ``ttyname(3)`` and ``crypt_r(3)`` instead of ``crypt(3)``.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, though, since you're not using crypt_r any more. :P

(I should have seen that last time I looked at the message.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case will only leave this PR for the ttyname_r, and then create another one for just the error checking of crypt, and will change the news message accordingly.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

* Use dynamically-allocated buffer for ttyname_r buffer
* In case there is an error with the crypt/crypt_r then raise the error,
instead of failing silently.
* Check for EINVAL (invalid argument) in errno when adding not supported
cryptographic methods, such as Blowfish in the case of glibc, but raise
other errors.
* Use _SC_TTY_MAX_LENGTH for TTY_NAME_MAX

Signed-off-by: Antonio Gutierrez <chibby0ne@gmail.com>
@chibby0ne
Copy link
Contributor Author

I have made the requested changes; please review again

@bedevere-bot
Copy link

Thanks for making the requested changes!

@benjaminp: please review the changes made to this pull request.

@bedevere-bot bedevere-bot requested a review from benjaminp October 5, 2019 19:29
@chibby0ne
Copy link
Contributor Author

@benjaminp What do you think I should name the other PR? AFAIK the PR branch should match the name of the issue. Should I name fix-issue-36161_part2?

@chibby0ne chibby0ne changed the title bpo-36161: Use thread-safe functions instead of unsafe ones (crypt, ttyname) bpo-36161: Use thread-safe function ttyname_r instead of unsafe one ttyname Oct 5, 2019
@chibby0ne
Copy link
Contributor Author

Hi @benjaminp, the error check for crypt/crypt_r was moved to: #16599

@benjaminp benjaminp merged commit 594e2ed into python:master Oct 9, 2019
@benjaminp
Copy link
Contributor

Thank you for the PR.

jacobneiltaylor pushed a commit to jacobneiltaylor/cpython that referenced this pull request Dec 5, 2019
…onGH-14868)

Signed-off-by: Antonio Gutierrez <chibby0ne@gmail.com>
erlend-aasland added a commit to erlend-aasland/cpython that referenced this pull request Jan 4, 2025
PR python#14868 replaced the ttyname() call with ttyname_r(), but the old
check remained.
erlend-aasland added a commit that referenced this pull request Jan 7, 2025
PR #14868 replaced the ttyname() call with ttyname_r(), but the old
check remained.
erlend-aasland added a commit to erlend-aasland/cpython that referenced this pull request Jan 7, 2025
…ython#128503)

(cherry picked from commit e08b282)

PR python#14868 replaced the ttyname() call with ttyname_r(), but the old
check remained.
erlend-aasland added a commit that referenced this pull request Jan 7, 2025
…) (#128599)

(cherry picked from commit e08b282)

PR #14868 replaced the ttyname() call with ttyname_r(), but the old
check remained.
srinivasreddy pushed a commit to srinivasreddy/cpython that referenced this pull request Jan 8, 2025
…#128503)

PR python#14868 replaced the ttyname() call with ttyname_r(), but the old
check remained.
erlend-aasland added a commit that referenced this pull request Jan 11, 2025
…) (#128598)

(cherry picked from commit e08b282)

PR #14868 replaced the ttyname() call with ttyname_r(), but the old
check remained.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants