Issue #9659 has been updated by Jared Jennings.

File 001-detect-digest-failure.patch added
File 002-builtin-indicate-digest-failure.patch added
File 003-digest-openssl-md5-use-evp-api.patch added

Attached are three cumulative patches against source:/trunk@45452.

The first, 001-detect-digest-failure, changes the prototypes of digest initialization and finalization functions in the digest extension to return int instead of void; changes digest.c to check the return value of the initialization function and raise an exception in case of failure; and bumps the digest API version from 2 to 3.

The second, 002-builtin-indicate-digest-failure, changes the built-in digest implementations so that their initialization and finalization functions return an int, 1 for success or 0 for failure, as the OpenSSL functions return.

The third, 003-digest-openssl-md5-use-evp-api, changes the OpenSSL implementation of the md5 algorithm to use functions from `openssl/evp.h` rather than `openssl/md5.h`. The old, deprecated `MD5_Init` function calls `abort(3)` if used in FIPS-compliant mode, killing the interpreter; the `EVP_DigestInit_ex` function returns 0 to indicate initialization failure instead.

With these patches:

~~~
[vagrant@localhost ruby]$ OPENSSL_FORCE_FIPS_MODE= ruby -v -rdigest -e "puts Digest::MD5.hexdigest('hi')" 
ruby 2.2.0dev (2014-03-27) [x86_64-linux]
-e:1:in `digest': Digest initialization failed. (RuntimeError)
	from -e:1:in `hexdigest'
	from -e:1:in `<main>'
~~~

I think further improvement is possible. Generally, it appears that the functions and types used in the builtin digest algorithm implementations are made to mirror the `MD5_*`, `RIPEMD160_*`, etc APIs from OpenSSL. Since I'm moving the `ossl` implementations to use the `EVP_*` API instead, I think the Right Thing to do here would be to change the builtins to mirror that newer API. If someone else agrees, I can produce the patches; until then, I have tried to make the smallest patches possible.

About 001, I don't know the consequences of bumping the digest API version, and I didn't provide any migration code that will make code written against the version-2 API work with the version-3 API. Also I don't know if the exception raised in the case of digest failure is the right class of exception.

003 only changes the ossl implementation of MD5, not any of the other algorithms. To keep the patch size down, I hardcoded the digest and block size constants. This isn't very DRY. The larger changes I alluded to above could fix it.

I don't know if tests need to be added for this code, but there are none in the patches.

----------------------------------------
Bug #9659: crash in FIPS mode after unchecked algo->init_func failure
https://bugs.ruby-lang.org/issues/9659#change-45975

* Author: Jared Jennings
* Status: Open
* Priority: Normal
* Assignee: 
* Category: ext
* Target version: current: 2.2.0
* ruby -v: ruby 1.8.7 (2011-06-30 patchlevel 352) [x86_64-linux]
* Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN
----------------------------------------
This is just like #4944, but in the `digest` extension instead of the `openssl` extension.

On my host, which is configured for FIPS 140-2 compliance (this is a U.S. Government security standard), OpenSSL refuses to perform an MD5 checksum. It indicates this refusal when the digest algorithm initialization function is called: this function returns a 0 indicating failure instead of a 1 indicating success. But it's just a bunch of arithmetic; how can it fail? So the return code is ignored. But if the initialization fails, and we go on trying to use the algorithm, the Ruby interpreter crashes:

~~~
  $ OPENSSL_FORCE_FIPS_MODE= ruby -rdigest -e "puts Digest::MD5.hexdigest('hi')"
  md5_dgst.c(78): OpenSSL internal error, assertion failed: Digest MD5 forbidden in FIPS mode!
  Aborted (core dumped)
~~~

The digest extension, in the `rb_digest_base_alloc`, `rb_digest_base_reset`, and `rb_digest_base_finish` functions, is ignoring the return code of `algo->init_func`. If OpenSSL is present at build time, `algo->init_func` works out to be the `MD5_Init` function from OpenSSL. This function, according to its man page, returns a 1 for success or 0 for failure.

I see the problem under Ruby 1.8.7 as patched by Red Hat; I can't easily build the trunk on my system, but it looks like in r43668 the return value still isn't being checked in these three places:

 * source:ext/digest/digest.c@43668#L551
 * source:ext/digest/digest.c@43668#L589
 * source:ext/digest/digest.c@43668#L627

---Files--------------------------------
002-builtin-indicate-digest-failure.patch (10.4 KB)
001-detect-digest-failure.patch (2.12 KB)
003-digest-openssl-md5-use-evp-api.patch (1.8 KB)


-- 
https://bugs.ruby-lang.org/