Skip to content

Conversation

@headius
Copy link
Contributor

@headius headius commented Jun 27, 2025

Description

JWT appears to reuse these JWA instances across threads, which can lead to them stepping on each other via the shared OpenSSL::Digest instance. This causes decoding to fail verification, likely because the digest contains an amalgam of data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest, avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only one thread at a time can be calculating a digest against a given OpenSSL::Digest instance, due to the VM lock.

Fixes #696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

Checklist

Before the PR can be merged be sure the following are checked:

  • There are tests for the fix or feature added/changed
  • A description of the changes and a reference to the PR has been added to CHANGELOG.md. More details in the CONTRIBUTING.md

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez
@headius
Copy link
Contributor Author

headius commented Jun 27, 2025

Some variation of the script provided in #696 could probably be adapted as a test for the ECDSA part of the fix. I am not familiar enough with the libraries involved to know how to modify that script to test the RSA part of the fix.

headius added 3 commits June 26, 2025 21:22
The @digest instance variable now contains the name to the digest
to be used.

See jwt#697
This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.
@headius
Copy link
Contributor Author

headius commented Jun 27, 2025

I've adapted the #696 example for an ECDSA test.

@anakinj
Copy link
Member

anakinj commented Jun 27, 2025

Looks solid @headius and thanks for the fix. If you would address the issues raised by the robot we are good to go.

@anakinj
Copy link
Member

anakinj commented Jun 28, 2025

Re-enabled the Truffleruby CI step in #698. I can confirm that the provided spec is indeed failing for that.

@headius
Copy link
Contributor Author

headius commented Jun 28, 2025

I will fix the Rubocop issues and ping you when that's ready.

I am curious... why no JRuby in the test matrix before? I'll add it, but it is troubling that it wasn't there to begin with.

@headius
Copy link
Contributor Author

headius commented Jun 28, 2025

Well, I guess I answered my own question... there are just a few failures running on JRuby, and something that appears to hang. I will look into those separately from this PR (at some point).

I've made the Rubocop changes and this should now be ready to go.

@anakinj anakinj merged commit 7231ef5 into jwt:main Jun 28, 2025
14 checks passed
@anakinj
Copy link
Member

anakinj commented Jun 28, 2025

Thanks for the fix @headius. The fix is included in the latest patch release.

Cannot remember if there has been any specific reason there is no jruby in the CI matrix. If it's possible to get it there i'm all for it.

anakinj pushed a commit to anakinj/ruby-jwt that referenced this pull request Jun 29, 2025
* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add jwt#697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See jwt#697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.

* Fixes for Rubocop
anakinj pushed a commit to anakinj/ruby-jwt that referenced this pull request Jun 29, 2025
* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add jwt#697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See jwt#697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.

* Fixes for Rubocop
anakinj pushed a commit to anakinj/ruby-jwt that referenced this pull request Jun 29, 2025
* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add jwt#697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See jwt#697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.

* Fixes for Rubocop
anakinj pushed a commit to anakinj/ruby-jwt that referenced this pull request Jun 29, 2025
* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add jwt#697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See jwt#697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.

* Fixes for Rubocop
anakinj pushed a commit to anakinj/ruby-jwt that referenced this pull request Jun 29, 2025
* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add jwt#697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See jwt#697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.

* Fixes for Rubocop
anakinj pushed a commit to anakinj/ruby-jwt that referenced this pull request Jun 29, 2025
* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes jwt#696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add jwt#697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See jwt#697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in jwt#696 and provides a test for
the ECDSA part of the fix in jwt#697.

* Fixes for Rubocop
anakinj added a commit that referenced this pull request Jun 29, 2025
Avoid using the same digest across calls (#697)

* Avoid using the same digest across calls

JWT appears to reuse these JWA instances across threads, which
can lead to them stepping on each other via the shared
OpenSSL::Digest instance. This causes decoding to fail
verification, likely because the digest contains an amalgam of
data from the different threads.

This patch creates a new OpenSSL::Digest for each use, avoiding
the threading issue.

Note that the HMAC JWA already calls OpenSSL::HMAC.digest,
avoiding the shared state, and the others do not use digest.

The original code does not fail on CRuby most likely because only
one thread at a time can be calculating a digest against a given
OpenSSL::Digest instance, due to the VM lock.

Fixes #696

Addresses the issue reported in jruby/jruby#8504 by @mohamedhafez

* Add #697 to changelog

* Modify Rsa digest name test for new structure

The @digest instance variable now contains the name to the digest
to be used.

See #697

* Add test for concurrent encode/decode using ECDSA

This is adapted from the script in #696 and provides a test for
the ECDSA part of the fix in #697.

* Fixes for Rubocop

Co-authored-by: Charles Oliver Nutter <[email protected]>
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.

Concurrency issue when encoding/decoding across threads

2 participants