-
Notifications
You must be signed in to change notification settings - Fork 30.2k
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
crypto: allocate more memory for cipher.update() #20370
Conversation
one question: should we put an assertion to check the |
@yhwang I think that makes sense, yes. 👍 /cc @nodejs/crypto |
Is there no 3DES support in Node 9? I tried the test in Node 9 and it threw |
@addaleax Let me do it. thanks @ryzokuken does it means that I should label this with |
06718c4
to
4c995d4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM as a fix.
Marking this as security as I wrote a report for the Node.js security team a couple of days ago. An attacker can - hypothetically - write eight bytes into (un)allocated heap and at least partially control their contents. |
@yhwang I think @tniessen and @addaleax would know better. AFAIK, ~/Code/temp/node
❯ node crypto.js
node(79558,0x7fffa1d72380) malloc: *** error for object 0x10250fd60: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
[1] 79558 abort node crypto.js but yeah, label it so that it doesn't land on Node.js 6, 8 and 9 (if I hope this clarifies everything. |
I mean, the dont-land labels depend on a couple factors – as in, what is the negative impact if we do merge this to an LTS line? Does it make backporting there easier? The only downside I can think of is slightly higher memory consumption, but as I understand it these chunks are not all that large to begin with. Plus, we could reduce that problem by doing a shrinking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bug is that key wrap algorithms overflow because of the envelope that's added?
Allocating double the block size doesn't look Obviously Correct to me as the envelope size has no direct relationship to the block size.
The reason this PR fixes the immediate issue is because openssl currently only supports AES and 3DES key wrap modes, where envelope size == block size by happenstance.
I think a better solution is to check first if EVP_CIPHER_mode(cipher) == EVP_CIPH_WRAP_MODE
and then check the NID against a whitelist with EVP_CIPHER_nid(cipher)
and throw an exception when we don't recognize it.
You can look up the NIDs with grep wrap deps/openssl/openssl/crypto/objects/obj_dat.h | grep NID
but note that some ciphers are disabled in our builds so you probably want to intersect with node -p 'crypto.getCiphers().filter(s => s.toLowerCase().includes("wrap"))'
.
Ideally, each whitelisted cipher should have a test case to make sure it's working as expected.
// then plaintext to store ciphertext. | ||
const test = { | ||
key: crypto.randomBytes(24), | ||
iv: crypto.randomBytes(0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd use fixed strings to keep the test deterministic.
@bnoordhuis I was thinking the same thing, but a better solution can still be implemented later on. This patch at least makes sure that we don't overwrite unallocated heap memory. (And yes, the connection between envelope size and blocksize is debatable.) |
I can live with that but I'd suggest the following:
That way there can be no hidden footguns or time bombs. @yhwang Ping me if you have questions or want help. |
I want to say: I love your comments and I will update my change according to your comments. @bnoordhuis I will ping you if I need help. I have a family event this weekend and will be back to this next Tuesday. I will do the change then. |
I am not sure if it's the envelope. Based on the spec it's the sha1 of the key: https://tools.ietf.org/html/rfc3217#section-2 For the key wrap algorithms, seems you can call
It shouldn't be specific to key wrap algorithms. We should do the check for the algorithms that we supports, right? |
If that works, that would be great, but I couldn't find a conclusive solution in the documentation. |
@tniessen I can't find documentation either. I read the logic in |
4c995d4
to
308d570
Compare
@tniessen I updated the change to get output length before memory allocation. @bnoordhuis please let me know should I add CHECK for key wrapping? (my question is should we do the CHECK for all supported algorithms but not only for key wrapping? If that's the case, should I do it separately?) For |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM assuming you checked the returned value manually and CI passes. I'd still prefer to see this behavior of EVP_CipherUpdate
documented just so we don't rely on undefined behavior, but that's probably beyond our power.
src/node_crypto.cc
Outdated
*out_len = 0; | ||
int buff_len = len + EVP_CIPHER_CTX_block_size(ctx_); | ||
// For key wrapping algorithms, get output size by calling | ||
// EVP_CipherUpdate() with null output |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Period at the end of the sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tniessen fixed. Thanks!
That's 3DES. AES is different: https://tools.ietf.org/html/rfc3394#page-4. I use 'envelope' as a catch-all for the key wrap data that's added.
I think they're the only ones that add extra data. But if there's a reliable generic way of detecting that upfront, so much the better. |
For key wrapping algorithms, calling EVP_CipherUpdate() with null output could obtain the size for the ciphertext. Then use the returned size to allocate output buffer. Also add a test case to verify des3-wrap. Signed-off-by: Yihong Wang <yh.wang@ibm.com>
308d570
to
a9b2153
Compare
For key wrapping algorithms, calling EVP_CipherUpdate() with null output could obtain the size for the ciphertext. Then use the returned size to allocate output buffer. Also add a test case to verify des3-wrap. Signed-off-by: Yihong Wang <yh.wang@ibm.com> PR-URL: #20370 Fixes: #19655 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
Landed in f7cdeba. |
For key wrapping algorithms, calling EVP_CipherUpdate() with null output could obtain the size for the ciphertext. Then use the returned size to allocate output buffer. Also add a test case to verify des3-wrap. Signed-off-by: Yihong Wang <yh.wang@ibm.com> PR-URL: #20370 Fixes: #19655 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
Back-porters, this should land along with #20587. |
For key wrapping algorithms, calling EVP_CipherUpdate() with null output could obtain the size for the ciphertext. Then use the returned size to allocate output buffer. Also add a test case to verify des3-wrap. Signed-off-by: Yihong Wang <yh.wang@ibm.com> PR-URL: #20370 Fixes: #19655 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
For key wrapping algorithms, calling EVP_CipherUpdate() with null output could obtain the size for the ciphertext. Then use the returned size to allocate output buffer. Also add a test case to verify des3-wrap. Signed-off-by: Yihong Wang <yh.wang@ibm.com> PR-URL: nodejs#20370 Fixes: nodejs#19655 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
For key wrapping algorithms, calling EVP_CipherUpdate() with null output could obtain the size for the ciphertext. Then use the returned size to allocate output buffer. Also add a test case to verify des3-wrap. Signed-off-by: Yihong Wang <yh.wang@ibm.com> Backport-PR-URL: #20706 PR-URL: #20370 Fixes: #19655 Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
For some algorithms, they need extra 2x blocksize to store the ciphertext in
order to avoid invalid write. Also add a test case to verify it.
refs: #19655
Signed-off-by: Yihong Wang yh.wang@ibm.com
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes