This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Errno conflicts in ssl.SSLError
Type: behavior Stage: needs patch
Components: SSL Versions: Python 3.7, Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Ben.Darnell, alex, christian.heimes, dstufft, janssen, martin.panter, nagle, pitrou
Priority: normal Keywords:

Created on 2015-03-05 04:10 by Ben.Darnell, last changed 2022-04-11 14:58 by admin.

Messages (7)
msg237239 - (view) Author: Ben Darnell (Ben.Darnell) * Date: 2015-03-05 04:10
ssl.SSLError is a subclass of OSError but uses error codes that come from a different scope than the standard errno constants and may have conflicts. For example, on my system (OSX), ssl.SSL_ERROR_WANT_X509_LOOKUP and errno.EINTR have the same value. This would cause problems for code like the following:

  def f():
    while True:
      try:
        return ssl_sock.read()
      except OSError as e:
        if e.errno == errno.EINTR:
          continue
        raise

This function would loop endlessly on ssl.SSL_ERROR_WANT_X509_LOOKUP. (Note that I have not run into any problem like this in practice so this is not a severe issue; I just stumbled across the possibility). The EINTR case is moot now that Python itself retries on EINTR, but other conflicts are possible (the problem is also mitigated by the fact that most of the interesting errnos now have dedicated exception subtypes)

To use OSError.errno correctly, it is currently necessary to check the exact type of the exception; it is not enough to do subclass matching as is usual for exceptions. To remedy this, I propose either changing the numeric constants in the SSL module (if these are not required to match constants defined in openssl), or adding an attribute to OSError like 'errno_scope' to be used when interpreting the errno field.
msg237487 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-03-08 00:55
[padding]

This behaviour of returning an SSL-specific error code in the “errno” attribute is not documented. The “errno” attribute is actually specified to hold a POSIX error code, and I don’t think this specification should be changed.

I don’t see how checking for an “errno_scope” attribute is any better than checking for isinstance(exception, SSLError). I think the SSL error codes should be removed from “errno”, or maybe moved to a new attribute (ssl_error?), like what is already done with the “winerror” attribute.
msg237508 - (view) Author: Ben Darnell (Ben.Darnell) * Date: 2015-03-08 03:17
I agree that SSLError should have used a different attribute, but it's too late for that - changing it would break any code currently relying on SSL errnos (in particular asynchronous code using the SSL_ERROR_WANT_{READ,WRITE} error codes for normal operation).

errno_scope is better than checking for isinstance(SSLError) because it lets code that only wants posix errno to say so, instead of having to know about and blacklist every OSError subclass that abuses the errno attribute (is SSLError the only one?)
msg237512 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-03-08 06:24
Okay I see your point about backwards compatibility now. Indeed, I have written code myself before Python 3.3 that used to inspect err.args[0], and it would suffer a similar problem if I had not updated it to use the new Python 3.3 exception subclasses.

Below I have written a comparison of two options that have been proposed. Both have disadvantages and would potentially require changes in user code.

A third middle-ground option might be to deprecate OSError.errno as well, and replace it with OSError.posix_errno or something. But I would still prefer the option to stop setting SSLError.errno, even though it breaks backwards compatibility. I think we can at least agree that “errno” and “args” should be deprecated for SSLError instances.

## Option 1: Add OSError.errno_scope ##

To avoid the bug in new Python versions we would still have to modify code handling a general OSError (EnvironmentError), for example:

# Buggy handler compatible with Python 3.2
try:
  return ssl_sock.read()
except EnvironmentError as e:
  if e.errno != errno.EINTR:
    raise

# Fixed handler, supporting new Python versions only
try:
  return ssl_sock.read()
except OSError as e:
  if e.errno_scope != POSIX or e.errno != errno.EINTR:
    raise

Advantages:
* Existing code still works if you don’t care about this bug
* No code changes required in SSL error handlers

## Option 2: Stop setting SSLError.errno ##

Existing SSLError handlers written for Python < 3.3 would stop catching exceptions, because “errno” defaults to None. This would probably cause an unhandled exception, and could be fixed as follows:

# Broken handler written for Python 3.2
try:
  return ssl_sock.read()
except SSLError as e:
  if e.errno != SSL_ERROR_WANT_READ:
    raise
  # Handle non-blocking read

# Fixed handler, supporting Python 3.3+ only
try:
  return ssl_sock.read()
except SSLWantReadError as e:
  # Handle non-blocking read

Advantages:
* Affected code should be easy to find due to breakage
* Only requires the user to change code that directly handles SSL errors
* No changes for code already written for Python 3.3
* Code changes only when relying on the undocumented errno behaviour, which is already asking for trouble
msg237513 - (view) Author: Martin Panter (martin.panter) * (Python committer) Date: 2015-03-08 06:44
Sorry I take back my claim that the error codes were undocumented; see the example code in <https://docs.python.org/3.2/library/ssl.html#ssl-nonblocking>.

Perhaps the most compatible way to fix this would be to change the SSL error codes to something (custom class?) that does not compare equal to any possible POSIX error code.
msg237555 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2015-03-08 19:33
Theoretically, SSLError could stop inheriting from OSError. Practically, it may break a lot of code (though in a rather noisy way that is easy to spot).

Otherwise, the best effort fix is to make our SSL error codes start at a high number, e.g. 2000 (but not too high, so as not to overlap with Windows' socket error codes, which start at 10000 IIRC). See the "py_ssl_error" enum at the beginning of _ssl.c.
msg239837 - (view) Author: John Nagle (nagle) Date: 2015-04-01 19:19
If SSL error reporting is getting some attention, something should be done to provide better text messages for the SSL errors.  All certificate verify exceptions return the string "certificate verify failed (_ssl.c:581)". The line number in _ssl.c is not particularly useful to end users. OpenSSL has more specific messages, but they're not making it to the Python level.

'python ssl "certificate verify failed"' has 17,000 hits in Google, which indicates users need more help dealing with this class of error.
History
Date User Action Args
2022-04-11 14:58:13adminsetgithub: 67776
2017-09-07 20:51:14christian.heimessetassignee: christian.heimes ->
2016-09-15 07:54:25christian.heimessetassignee: christian.heimes
components: + SSL
2016-09-08 15:22:32giampaolo.rodolasetnosy: - giampaolo.rodola
2016-09-08 15:19:51christian.heimessetversions: + Python 3.6, Python 3.7, - Python 3.4, Python 3.5
2015-04-01 19:19:19naglesetnosy: + nagle
messages: + msg239837
2015-03-08 19:33:02pitrousetmessages: + msg237555
stage: needs patch
2015-03-08 06:44:12martin.pantersetmessages: + msg237513
2015-03-08 06:24:40martin.pantersetmessages: + msg237512
2015-03-08 03:17:34Ben.Darnellsetmessages: + msg237508
2015-03-08 00:55:30martin.pantersetnosy: + martin.panter
messages: + msg237487
2015-03-05 05:37:32ned.deilysetnosy: + janssen, pitrou, giampaolo.rodola, christian.heimes, alex, dstufft

versions: + Python 3.4, Python 3.5
2015-03-05 04:10:08Ben.Darnellcreate