/src/node/src/crypto/crypto_bio.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright Joyent, Inc. and other Node contributors. |
2 | | // |
3 | | // Permission is hereby granted, free of charge, to any person obtaining a |
4 | | // copy of this software and associated documentation files (the |
5 | | // "Software"), to deal in the Software without restriction, including |
6 | | // without limitation the rights to use, copy, modify, merge, publish, |
7 | | // distribute, sublicense, and/or sell copies of the Software, and to permit |
8 | | // persons to whom the Software is furnished to do so, subject to the |
9 | | // following conditions: |
10 | | // |
11 | | // The above copyright notice and this permission notice shall be included |
12 | | // in all copies or substantial portions of the Software. |
13 | | // |
14 | | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
15 | | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
16 | | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
17 | | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
18 | | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
19 | | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
20 | | // USE OR OTHER DEALINGS IN THE SOFTWARE. |
21 | | |
22 | | #include "crypto/crypto_bio.h" |
23 | | #include "base_object-inl.h" |
24 | | #include "memory_tracker-inl.h" |
25 | | #include "util-inl.h" |
26 | | |
27 | | #include <openssl/bio.h> |
28 | | |
29 | | #include <climits> |
30 | | #include <cstring> |
31 | | |
32 | | namespace node { |
33 | | namespace crypto { |
34 | | |
35 | 1.94k | BIOPointer NodeBIO::New(Environment* env) { |
36 | 1.94k | BIOPointer bio(BIO_new(GetMethod())); |
37 | 1.94k | if (bio && env != nullptr) |
38 | 1.79k | NodeBIO::FromBIO(bio.get())->env_ = env; |
39 | 1.94k | return bio; |
40 | 1.94k | } |
41 | | |
42 | | |
43 | 147 | BIOPointer NodeBIO::NewFixed(const char* data, size_t len, Environment* env) { |
44 | 147 | BIOPointer bio = New(env); |
45 | | |
46 | 147 | if (!bio || |
47 | 147 | len > INT_MAX || |
48 | 147 | BIO_write(bio.get(), data, len) != static_cast<int>(len) || |
49 | 147 | BIO_set_mem_eof_return(bio.get(), 0) != 1) { |
50 | 0 | return BIOPointer(); |
51 | 0 | } |
52 | | |
53 | 147 | return bio; |
54 | 147 | } |
55 | | |
56 | | |
57 | 1.94k | int NodeBIO::New(BIO* bio) { |
58 | 1.94k | BIO_set_data(bio, new NodeBIO()); |
59 | 1.94k | BIO_set_init(bio, 1); |
60 | | |
61 | 1.94k | return 1; |
62 | 1.94k | } |
63 | | |
64 | | |
65 | 1.94k | int NodeBIO::Free(BIO* bio) { |
66 | 1.94k | if (bio == nullptr) |
67 | 0 | return 0; |
68 | | |
69 | 1.94k | if (BIO_get_shutdown(bio)) { |
70 | 1.94k | if (BIO_get_init(bio) && BIO_get_data(bio) != nullptr) { |
71 | 1.94k | delete FromBIO(bio); |
72 | 1.94k | BIO_set_data(bio, nullptr); |
73 | 1.94k | } |
74 | 1.94k | } |
75 | | |
76 | 1.94k | return 1; |
77 | 1.94k | } |
78 | | |
79 | | |
80 | 0 | int NodeBIO::Read(BIO* bio, char* out, int len) { |
81 | 0 | BIO_clear_retry_flags(bio); |
82 | |
|
83 | 0 | NodeBIO* nbio = FromBIO(bio); |
84 | 0 | int bytes = nbio->Read(out, len); |
85 | |
|
86 | 0 | if (bytes == 0) { |
87 | 0 | bytes = nbio->eof_return(); |
88 | 0 | if (bytes != 0) { |
89 | 0 | BIO_set_retry_read(bio); |
90 | 0 | } |
91 | 0 | } |
92 | |
|
93 | 0 | return bytes; |
94 | 0 | } |
95 | | |
96 | | |
97 | 0 | char* NodeBIO::Peek(size_t* size) { |
98 | 0 | *size = read_head_->write_pos_ - read_head_->read_pos_; |
99 | 0 | return read_head_->data_ + read_head_->read_pos_; |
100 | 0 | } |
101 | | |
102 | | |
103 | 0 | size_t NodeBIO::PeekMultiple(char** out, size_t* size, size_t* count) { |
104 | 0 | Buffer* pos = read_head_; |
105 | 0 | size_t max = *count; |
106 | 0 | size_t total = 0; |
107 | |
|
108 | 0 | size_t i; |
109 | 0 | for (i = 0; i < max; i++) { |
110 | 0 | size[i] = pos->write_pos_ - pos->read_pos_; |
111 | 0 | total += size[i]; |
112 | 0 | out[i] = pos->data_ + pos->read_pos_; |
113 | | |
114 | | /* Don't get past write head */ |
115 | 0 | if (pos == write_head_) |
116 | 0 | break; |
117 | 0 | else |
118 | 0 | pos = pos->next_; |
119 | 0 | } |
120 | |
|
121 | 0 | if (i == max) |
122 | 0 | *count = i; |
123 | 0 | else |
124 | 0 | *count = i + 1; |
125 | |
|
126 | 0 | return total; |
127 | 0 | } |
128 | | |
129 | | |
130 | 147 | int NodeBIO::Write(BIO* bio, const char* data, int len) { |
131 | 147 | BIO_clear_retry_flags(bio); |
132 | | |
133 | 147 | FromBIO(bio)->Write(data, len); |
134 | | |
135 | 147 | return len; |
136 | 147 | } |
137 | | |
138 | | |
139 | 0 | int NodeBIO::Puts(BIO* bio, const char* str) { |
140 | 0 | return Write(bio, str, strlen(str)); |
141 | 0 | } |
142 | | |
143 | | |
144 | 3.27k | int NodeBIO::Gets(BIO* bio, char* out, int size) { |
145 | 3.27k | NodeBIO* nbio = FromBIO(bio); |
146 | | |
147 | 3.27k | if (nbio->Length() == 0) |
148 | 0 | return 0; |
149 | | |
150 | 3.27k | int i = nbio->IndexOf('\n', size); |
151 | | |
152 | | // Include '\n', if it's there. If not, don't read off the end. |
153 | 3.27k | if (i < size && i >= 0 && static_cast<size_t>(i) < nbio->Length()) |
154 | 3.13k | i++; |
155 | | |
156 | | // Shift `i` a bit to nullptr-terminate string later |
157 | 3.27k | if (size == i) |
158 | 0 | i--; |
159 | | |
160 | | // Flush read data |
161 | 3.27k | nbio->Read(out, i); |
162 | | |
163 | 3.27k | out[i] = 0; |
164 | | |
165 | 3.27k | return i; |
166 | 3.27k | } |
167 | | |
168 | | |
169 | | long NodeBIO::Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int) |
170 | 147 | void* ptr) { |
171 | 147 | NodeBIO* nbio; |
172 | 147 | long ret; // NOLINT(runtime/int) |
173 | | |
174 | 147 | nbio = FromBIO(bio); |
175 | 147 | ret = 1; |
176 | | |
177 | 147 | switch (cmd) { |
178 | 0 | case BIO_CTRL_RESET: |
179 | 0 | nbio->Reset(); |
180 | 0 | break; |
181 | 0 | case BIO_CTRL_EOF: |
182 | 0 | ret = nbio->Length() == 0; |
183 | 0 | break; |
184 | 147 | case BIO_C_SET_BUF_MEM_EOF_RETURN: |
185 | 147 | nbio->set_eof_return(num); |
186 | 147 | break; |
187 | 0 | case BIO_CTRL_INFO: |
188 | 0 | ret = nbio->Length(); |
189 | 0 | if (ptr != nullptr) |
190 | 0 | *reinterpret_cast<void**>(ptr) = nullptr; |
191 | 0 | break; |
192 | 0 | case BIO_C_SET_BUF_MEM: |
193 | 0 | UNREACHABLE("Can't use SET_BUF_MEM_PTR with NodeBIO"); |
194 | 0 | case BIO_C_GET_BUF_MEM_PTR: |
195 | 0 | UNREACHABLE("Can't use GET_BUF_MEM_PTR with NodeBIO"); |
196 | 0 | case BIO_CTRL_GET_CLOSE: |
197 | 0 | ret = BIO_get_shutdown(bio); |
198 | 0 | break; |
199 | 0 | case BIO_CTRL_SET_CLOSE: |
200 | 0 | BIO_set_shutdown(bio, num); |
201 | 0 | break; |
202 | 0 | case BIO_CTRL_WPENDING: |
203 | 0 | ret = 0; |
204 | 0 | break; |
205 | 0 | case BIO_CTRL_PENDING: |
206 | 0 | ret = nbio->Length(); |
207 | 0 | break; |
208 | 0 | case BIO_CTRL_DUP: |
209 | 0 | case BIO_CTRL_FLUSH: |
210 | 0 | ret = 1; |
211 | 0 | break; |
212 | 0 | case BIO_CTRL_PUSH: |
213 | 0 | case BIO_CTRL_POP: |
214 | 0 | default: |
215 | 0 | ret = 0; |
216 | 0 | break; |
217 | 147 | } |
218 | 147 | return ret; |
219 | 147 | } |
220 | | |
221 | | |
222 | 1.94k | const BIO_METHOD* NodeBIO::GetMethod() { |
223 | | // Static initialization ensures that this is safe to use concurrently. |
224 | 1.94k | static const BIO_METHOD* method = [&]() { |
225 | 1 | BIO_METHOD* method = BIO_meth_new(BIO_TYPE_MEM, "node.js SSL buffer"); |
226 | 1 | BIO_meth_set_write(method, Write); |
227 | 1 | BIO_meth_set_read(method, Read); |
228 | 1 | BIO_meth_set_puts(method, Puts); |
229 | 1 | BIO_meth_set_gets(method, Gets); |
230 | 1 | BIO_meth_set_ctrl(method, Ctrl); |
231 | 1 | BIO_meth_set_create(method, New); |
232 | 1 | BIO_meth_set_destroy(method, Free); |
233 | 1 | return method; |
234 | 1 | }(); |
235 | | |
236 | 1.94k | return method; |
237 | 1.94k | } |
238 | | |
239 | | |
240 | 3.27k | void NodeBIO::TryMoveReadHead() { |
241 | | // `read_pos_` and `write_pos_` means the position of the reader and writer |
242 | | // inside the buffer, respectively. When they're equal - its safe to reset |
243 | | // them, because both reader and writer will continue doing their stuff |
244 | | // from new (zero) positions. |
245 | 3.42k | while (read_head_->read_pos_ != 0 && |
246 | 3.42k | read_head_->read_pos_ == read_head_->write_pos_) { |
247 | | // Reset positions |
248 | 147 | read_head_->read_pos_ = 0; |
249 | 147 | read_head_->write_pos_ = 0; |
250 | | |
251 | | // Move read_head_ forward, just in case if there're still some data to |
252 | | // read in the next buffer. |
253 | 147 | if (read_head_ != write_head_) |
254 | 0 | read_head_ = read_head_->next_; |
255 | 147 | } |
256 | 3.27k | } |
257 | | |
258 | | |
259 | 3.27k | size_t NodeBIO::Read(char* out, size_t size) { |
260 | 3.27k | size_t bytes_read = 0; |
261 | 3.27k | size_t expected = Length() > size ? size : Length(); |
262 | 3.27k | size_t offset = 0; |
263 | 3.27k | size_t left = size; |
264 | | |
265 | 6.55k | while (bytes_read < expected) { |
266 | 3.27k | CHECK_LE(read_head_->read_pos_, read_head_->write_pos_); |
267 | 3.27k | size_t avail = read_head_->write_pos_ - read_head_->read_pos_; |
268 | 3.27k | if (avail > left) |
269 | 3.13k | avail = left; |
270 | | |
271 | | // Copy data |
272 | 3.27k | if (out != nullptr) |
273 | 3.27k | memcpy(out + offset, read_head_->data_ + read_head_->read_pos_, avail); |
274 | 3.27k | read_head_->read_pos_ += avail; |
275 | | |
276 | | // Move pointers |
277 | 3.27k | bytes_read += avail; |
278 | 3.27k | offset += avail; |
279 | 3.27k | left -= avail; |
280 | | |
281 | 3.27k | TryMoveReadHead(); |
282 | 3.27k | } |
283 | 3.27k | CHECK_EQ(expected, bytes_read); |
284 | 3.27k | length_ -= bytes_read; |
285 | | |
286 | | // Free all empty buffers, but write_head's child |
287 | 3.27k | FreeEmpty(); |
288 | | |
289 | 3.27k | return bytes_read; |
290 | 3.27k | } |
291 | | |
292 | | |
293 | 3.27k | void NodeBIO::FreeEmpty() { |
294 | 3.27k | if (write_head_ == nullptr) |
295 | 0 | return; |
296 | 3.27k | Buffer* child = write_head_->next_; |
297 | 3.27k | if (child == write_head_ || child == read_head_) |
298 | 3.27k | return; |
299 | 0 | Buffer* cur = child->next_; |
300 | 0 | if (cur == write_head_ || cur == read_head_) |
301 | 0 | return; |
302 | | |
303 | 0 | Buffer* prev = child; |
304 | 0 | while (cur != read_head_) { |
305 | 0 | CHECK_NE(cur, write_head_); |
306 | 0 | CHECK_EQ(cur->write_pos_, cur->read_pos_); |
307 | | |
308 | 0 | Buffer* next = cur->next_; |
309 | 0 | delete cur; |
310 | 0 | cur = next; |
311 | 0 | } |
312 | 0 | prev->next_ = cur; |
313 | 0 | } |
314 | | |
315 | | |
316 | 3.27k | size_t NodeBIO::IndexOf(char delim, size_t limit) { |
317 | 3.27k | size_t bytes_read = 0; |
318 | 3.27k | size_t max = Length() > limit ? limit : Length(); |
319 | 3.27k | size_t left = limit; |
320 | 3.27k | Buffer* current = read_head_; |
321 | | |
322 | 3.42k | while (bytes_read < max) { |
323 | 3.27k | CHECK_LE(current->read_pos_, current->write_pos_); |
324 | 3.27k | size_t avail = current->write_pos_ - current->read_pos_; |
325 | 3.27k | if (avail > left) |
326 | 2.67k | avail = left; |
327 | | |
328 | | // Walk through data |
329 | 3.27k | char* tmp = current->data_ + current->read_pos_; |
330 | 3.27k | size_t off = 0; |
331 | 220k | while (off < avail && *tmp != delim) { |
332 | 217k | off++; |
333 | 217k | tmp++; |
334 | 217k | } |
335 | | |
336 | | // Move pointers |
337 | 3.27k | bytes_read += off; |
338 | 3.27k | left -= off; |
339 | | |
340 | | // Found `delim` |
341 | 3.27k | if (off != avail) { |
342 | 3.13k | return bytes_read; |
343 | 3.13k | } |
344 | | |
345 | | // Move to next buffer |
346 | 147 | if (current->read_pos_ + avail == current->len_) { |
347 | 109 | current = current->next_; |
348 | 109 | } |
349 | 147 | } |
350 | 147 | CHECK_EQ(max, bytes_read); |
351 | | |
352 | 147 | return max; |
353 | 147 | } |
354 | | |
355 | | |
356 | 147 | void NodeBIO::Write(const char* data, size_t size) { |
357 | 147 | size_t offset = 0; |
358 | 147 | size_t left = size; |
359 | | |
360 | | // Allocate initial buffer if the ring is empty |
361 | 147 | TryAllocateForWrite(left); |
362 | | |
363 | 294 | while (left > 0) { |
364 | 147 | size_t to_write = left; |
365 | 147 | CHECK_LE(write_head_->write_pos_, write_head_->len_); |
366 | 147 | size_t avail = write_head_->len_ - write_head_->write_pos_; |
367 | | |
368 | 147 | if (to_write > avail) |
369 | 0 | to_write = avail; |
370 | | |
371 | | // Copy data |
372 | 147 | memcpy(write_head_->data_ + write_head_->write_pos_, |
373 | 147 | data + offset, |
374 | 147 | to_write); |
375 | | |
376 | | // Move pointers |
377 | 147 | left -= to_write; |
378 | 147 | offset += to_write; |
379 | 147 | length_ += to_write; |
380 | 147 | write_head_->write_pos_ += to_write; |
381 | 147 | CHECK_LE(write_head_->write_pos_, write_head_->len_); |
382 | | |
383 | | // Go to next buffer if there still are some bytes to write |
384 | 147 | if (left != 0) { |
385 | 0 | CHECK_EQ(write_head_->write_pos_, write_head_->len_); |
386 | 0 | TryAllocateForWrite(left); |
387 | 0 | write_head_ = write_head_->next_; |
388 | | |
389 | | // Additionally, since we're moved to the next buffer, read head |
390 | | // may be moved as well. |
391 | 0 | TryMoveReadHead(); |
392 | 0 | } |
393 | 147 | } |
394 | 147 | CHECK_EQ(left, 0); |
395 | 147 | } |
396 | | |
397 | | |
398 | 0 | char* NodeBIO::PeekWritable(size_t* size) { |
399 | 0 | TryAllocateForWrite(*size); |
400 | |
|
401 | 0 | size_t available = write_head_->len_ - write_head_->write_pos_; |
402 | 0 | if (*size == 0 || available <= *size) |
403 | 0 | *size = available; |
404 | |
|
405 | 0 | return write_head_->data_ + write_head_->write_pos_; |
406 | 0 | } |
407 | | |
408 | | |
409 | 0 | void NodeBIO::Commit(size_t size) { |
410 | 0 | write_head_->write_pos_ += size; |
411 | 0 | length_ += size; |
412 | 0 | CHECK_LE(write_head_->write_pos_, write_head_->len_); |
413 | | |
414 | | // Allocate new buffer if write head is full, |
415 | | // and there're no other place to go |
416 | 0 | TryAllocateForWrite(0); |
417 | 0 | if (write_head_->write_pos_ == write_head_->len_) { |
418 | 0 | write_head_ = write_head_->next_; |
419 | | |
420 | | // Additionally, since we're moved to the next buffer, read head |
421 | | // may be moved as well. |
422 | 0 | TryMoveReadHead(); |
423 | 0 | } |
424 | 0 | } |
425 | | |
426 | | |
427 | 147 | void NodeBIO::TryAllocateForWrite(size_t hint) { |
428 | 147 | Buffer* w = write_head_; |
429 | 147 | Buffer* r = read_head_; |
430 | | // If write head is full, next buffer is either read head or not empty. |
431 | 147 | if (w == nullptr || |
432 | 147 | (w->write_pos_ == w->len_ && |
433 | 147 | (w->next_ == r || w->next_->write_pos_ != 0))) { |
434 | 147 | size_t len = w == nullptr ? initial_ : |
435 | 147 | kThroughputBufferLength; |
436 | 147 | if (len < hint) |
437 | 109 | len = hint; |
438 | | |
439 | | // If there is a one time allocation size hint, use it. |
440 | 147 | if (allocate_hint_ > len) { |
441 | 0 | len = allocate_hint_; |
442 | 0 | allocate_hint_ = 0; |
443 | 0 | } |
444 | | |
445 | 147 | Buffer* next = new Buffer(env_, len); |
446 | | |
447 | 147 | if (w == nullptr) { |
448 | 147 | next->next_ = next; |
449 | 147 | write_head_ = next; |
450 | 147 | read_head_ = next; |
451 | 147 | } else { |
452 | 0 | next->next_ = w->next_; |
453 | 0 | w->next_ = next; |
454 | 0 | } |
455 | 147 | } |
456 | 147 | } |
457 | | |
458 | | |
459 | 0 | void NodeBIO::Reset() { |
460 | 0 | if (read_head_ == nullptr) |
461 | 0 | return; |
462 | | |
463 | 0 | while (read_head_->read_pos_ != read_head_->write_pos_) { |
464 | 0 | CHECK(read_head_->write_pos_ > read_head_->read_pos_); |
465 | | |
466 | 0 | length_ -= read_head_->write_pos_ - read_head_->read_pos_; |
467 | 0 | read_head_->write_pos_ = 0; |
468 | 0 | read_head_->read_pos_ = 0; |
469 | |
|
470 | 0 | read_head_ = read_head_->next_; |
471 | 0 | } |
472 | 0 | write_head_ = read_head_; |
473 | 0 | CHECK_EQ(length_, 0); |
474 | 0 | } |
475 | | |
476 | | |
477 | 1.94k | NodeBIO::~NodeBIO() { |
478 | 1.94k | if (read_head_ == nullptr) |
479 | 1.79k | return; |
480 | | |
481 | 147 | Buffer* current = read_head_; |
482 | 147 | do { |
483 | 147 | Buffer* next = current->next_; |
484 | 147 | delete current; |
485 | 147 | current = next; |
486 | 147 | } while (current != read_head_); |
487 | | |
488 | 147 | read_head_ = nullptr; |
489 | 147 | write_head_ = nullptr; |
490 | 147 | } |
491 | | |
492 | | |
493 | 8.20k | NodeBIO* NodeBIO::FromBIO(BIO* bio) { |
494 | 8.20k | CHECK_NOT_NULL(BIO_get_data(bio)); |
495 | 8.20k | return static_cast<NodeBIO*>(BIO_get_data(bio)); |
496 | 8.20k | } |
497 | | |
498 | | |
499 | | } // namespace crypto |
500 | | } // namespace node |