Skip to content

Commit 796eeb1

Browse files
committed
Improve jsbn fix.
- More verbose fix with indication it's a forge fix for jshn. - Avoid process spawning test. The tests should always pass. If they hang, that's a test failure. - Add changelog entry.
1 parent 9bb8d67 commit 796eeb1

File tree

3 files changed

+61
-42
lines changed

3 files changed

+61
-42
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,28 @@ Forge ChangeLog
33

44
## 1.4.0 - 2026-xx-xx
55

6+
### Security
7+
- **HIGH**: Denial of Service in `BigInteger.modInverse()`
8+
- A Denial of Service (DoS) vulnerability exists due to an infinite loop in
9+
the `BigInteger.modInverse()` function (inherited from the bundled jsbn
10+
library). When `modInverse()` is called with a zero value as input, the
11+
internal Extended Euclidean Algorithm enters an unreachable exit condition,
12+
causing the process to hang indefinitely and consume 100% CPU.
13+
- Reported by Kr0emer.
14+
- CVE ID: [CVE-2026-33891](https://www.cve.org/CVERecord?id=CVE-2026-33891)
15+
- GHSA ID: [GHSA-5gfm-wpxj-wjgq](https://github.com/digitalbazaar/forge/security/advisories/GHSA-5m6q-g25r-mvwx)
16+
617
### Changed
718
- [jsbn] Update to `jsbn` 1.4. Sync partly back to original style for easier
819
updates every decade or so.
920

21+
### Fixed
22+
- [jsbn] Fix `BigInteger.modInverse` to avoid an infinite loop and exit early
23+
with zero when the target object value is <= 0. Zero may not be strictly
24+
mathematically correct but aligns with current `jsbn` behavior returning zero
25+
in other situations. The alternate of a `RangeError` would diverge from the
26+
rest of the API.
27+
1028
## 1.3.3 - 2025-12-02
1129

1230
### Fixed

lib/jsbn.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1122,7 +1122,13 @@ function bnpModInt(n) {
11221122

11231123
// (public) 1/this % m (HAC 14.61)
11241124
function bnModInverse(m) {
1125-
if(this.signum() == 0) return BigInteger.ZERO;
1125+
// FORGE: jsbn fix
1126+
// avoid infinite loop
1127+
if(this.signum() == 0) {
1128+
// returning zero to align with similar behavior when no multiplicative
1129+
// inverse module m is found.
1130+
return BigInteger.ZERO;
1131+
}
11261132
var ac = m.isEven();
11271133
if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO;
11281134
var u = m.clone(), v = this.clone();

tests/unit/jsbn.js

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,40 @@
11
var ASSERT = require('assert');
2+
var JSBN = require('../../lib/jsbn');
23

3-
(function() {
4-
if(typeof process === 'undefined' ||
5-
!process.versions || !process.versions.node) {
6-
return;
7-
}
8-
9-
var moduleRequire = module.require ? module.require.bind(module) : require;
10-
var PATH = moduleRequire('path');
11-
var spawnSync = moduleRequire('child_process').spawnSync;
12-
13-
describe('jsbn', function() {
14-
it('should return 0 for BigInteger(0).modInverse(3) without hanging', function() {
15-
var script = [
16-
'var JSBN = require("./lib/jsbn");',
17-
'var BigInteger = JSBN.BigInteger;',
18-
'var zero = new BigInteger("0", 10);',
19-
'var mod = new BigInteger("3", 10);',
20-
'var inv = zero.modInverse(mod);',
21-
'process.stdout.write(inv.toString());'
22-
].join('\n');
23-
24-
var result = spawnSync(process.execPath, ['-e', script], {
25-
cwd: PATH.join(__dirname, '../..'),
26-
encoding: 'utf8',
27-
timeout: 2000
28-
});
29-
30-
if(result.error) {
31-
if(result.error.code === 'EPERM') {
32-
this.skip();
33-
return;
34-
}
35-
if(result.error.code === 'ETIMEDOUT') {
36-
ASSERT.fail('BigInteger(0).modInverse(3) timed out.');
37-
}
38-
throw result.error;
39-
}
40-
41-
ASSERT.equal(result.status, 0, result.stderr);
42-
ASSERT.equal(result.stdout, '0');
4+
describe.only('jsbn', function() {
5+
describe('GHSA-5m6q-g25r-mvwx', function() {
6+
// regression tests for GHSA-5m6q-g25r-mvwx
7+
// test BigInteger.modInverse does not infinite loop with 0 inputs.
8+
var BigInteger = JSBN.BigInteger;
9+
it('should test BigInteger(0).modInverse(0) returns 0', function() {
10+
var n = BigInteger.ZERO;
11+
var mod = BigInteger.ZERO;
12+
var inv = n.modInverse(mod);
13+
ASSERT(inv.equals(BigInteger.ZERO));
14+
});
15+
it('should test BigInteger(0).modInverse(3) returns 0', function() {
16+
var n = BigInteger.ZERO;
17+
var mod = new BigInteger('3', 10);
18+
var inv = n.modInverse(mod);
19+
ASSERT(inv.equals(BigInteger.ZERO));
20+
});
21+
it('should test BigInteger(3).modInverse(0) returns 0', function() {
22+
var n = new BigInteger('3', 10);
23+
var mod = BigInteger.ZERO;
24+
var inv = n.modInverse(mod);
25+
ASSERT(inv.equals(BigInteger.ZERO));
26+
});
27+
it('should test BigInteger(3).modInverse(3) returns 0', function() {
28+
var n = new BigInteger('3', 10);
29+
var mod = new BigInteger('3', 10);
30+
var inv = n.modInverse(mod);
31+
ASSERT(inv.equals(BigInteger.ZERO));
32+
});
33+
it('should test BigInteger(7).modInverse(20) returns 3', function() {
34+
var n = new BigInteger('7', 10);
35+
var mod = new BigInteger('20', 10);
36+
var inv = n.modInverse(mod);
37+
ASSERT(inv.equals(new BigInteger('3', 10)));
4338
});
4439
});
45-
})();
40+
});

0 commit comments

Comments
 (0)