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
New(Environment * env)35 BIOPointer NodeBIO::New(Environment* env) {
36 BIOPointer bio(BIO_new(GetMethod()));
37 if (bio && env != nullptr)
38 NodeBIO::FromBIO(bio.get())->env_ = env;
39 return bio;
40 }
41
42
NewFixed(const char * data,size_t len,Environment * env)43 BIOPointer NodeBIO::NewFixed(const char* data, size_t len, Environment* env) {
44 BIOPointer bio = New(env);
45
46 if (!bio ||
47 len > INT_MAX ||
48 BIO_write(bio.get(), data, len) != static_cast<int>(len) ||
49 BIO_set_mem_eof_return(bio.get(), 0) != 1) {
50 return BIOPointer();
51 }
52
53 return bio;
54 }
55
56
New(BIO * bio)57 int NodeBIO::New(BIO* bio) {
58 BIO_set_data(bio, new NodeBIO());
59 BIO_set_init(bio, 1);
60
61 return 1;
62 }
63
64
Free(BIO * bio)65 int NodeBIO::Free(BIO* bio) {
66 if (bio == nullptr)
67 return 0;
68
69 if (BIO_get_shutdown(bio)) {
70 if (BIO_get_init(bio) && BIO_get_data(bio) != nullptr) {
71 delete FromBIO(bio);
72 BIO_set_data(bio, nullptr);
73 }
74 }
75
76 return 1;
77 }
78
79
Read(BIO * bio,char * out,int len)80 int NodeBIO::Read(BIO* bio, char* out, int len) {
81 BIO_clear_retry_flags(bio);
82
83 NodeBIO* nbio = FromBIO(bio);
84 int bytes = nbio->Read(out, len);
85
86 if (bytes == 0) {
87 bytes = nbio->eof_return();
88 if (bytes != 0) {
89 BIO_set_retry_read(bio);
90 }
91 }
92
93 return bytes;
94 }
95
96
Peek(size_t * size)97 char* NodeBIO::Peek(size_t* size) {
98 *size = read_head_->write_pos_ - read_head_->read_pos_;
99 return read_head_->data_ + read_head_->read_pos_;
100 }
101
102
PeekMultiple(char ** out,size_t * size,size_t * count)103 size_t NodeBIO::PeekMultiple(char** out, size_t* size, size_t* count) {
104 Buffer* pos = read_head_;
105 size_t max = *count;
106 size_t total = 0;
107
108 size_t i;
109 for (i = 0; i < max; i++) {
110 size[i] = pos->write_pos_ - pos->read_pos_;
111 total += size[i];
112 out[i] = pos->data_ + pos->read_pos_;
113
114 /* Don't get past write head */
115 if (pos == write_head_)
116 break;
117 else
118 pos = pos->next_;
119 }
120
121 if (i == max)
122 *count = i;
123 else
124 *count = i + 1;
125
126 return total;
127 }
128
129
Write(BIO * bio,const char * data,int len)130 int NodeBIO::Write(BIO* bio, const char* data, int len) {
131 BIO_clear_retry_flags(bio);
132
133 FromBIO(bio)->Write(data, len);
134
135 return len;
136 }
137
138
Puts(BIO * bio,const char * str)139 int NodeBIO::Puts(BIO* bio, const char* str) {
140 return Write(bio, str, strlen(str));
141 }
142
143
Gets(BIO * bio,char * out,int size)144 int NodeBIO::Gets(BIO* bio, char* out, int size) {
145 NodeBIO* nbio = FromBIO(bio);
146
147 if (nbio->Length() == 0)
148 return 0;
149
150 int i = nbio->IndexOf('\n', size);
151
152 // Include '\n', if it's there. If not, don't read off the end.
153 if (i < size && i >= 0 && static_cast<size_t>(i) < nbio->Length())
154 i++;
155
156 // Shift `i` a bit to nullptr-terminate string later
157 if (size == i)
158 i--;
159
160 // Flush read data
161 nbio->Read(out, i);
162
163 out[i] = 0;
164
165 return i;
166 }
167
168
Ctrl(BIO * bio,int cmd,long num,void * ptr)169 long NodeBIO::Ctrl(BIO* bio, int cmd, long num, // NOLINT(runtime/int)
170 void* ptr) {
171 NodeBIO* nbio;
172 long ret; // NOLINT(runtime/int)
173
174 nbio = FromBIO(bio);
175 ret = 1;
176
177 switch (cmd) {
178 case BIO_CTRL_RESET:
179 nbio->Reset();
180 break;
181 case BIO_CTRL_EOF:
182 ret = nbio->Length() == 0;
183 break;
184 case BIO_C_SET_BUF_MEM_EOF_RETURN:
185 nbio->set_eof_return(num);
186 break;
187 case BIO_CTRL_INFO:
188 ret = nbio->Length();
189 if (ptr != nullptr)
190 *reinterpret_cast<void**>(ptr) = nullptr;
191 break;
192 case BIO_C_SET_BUF_MEM:
193 UNREACHABLE("Can't use SET_BUF_MEM_PTR with NodeBIO");
194 case BIO_C_GET_BUF_MEM_PTR:
195 UNREACHABLE("Can't use GET_BUF_MEM_PTR with NodeBIO");
196 case BIO_CTRL_GET_CLOSE:
197 ret = BIO_get_shutdown(bio);
198 break;
199 case BIO_CTRL_SET_CLOSE:
200 BIO_set_shutdown(bio, num);
201 break;
202 case BIO_CTRL_WPENDING:
203 ret = 0;
204 break;
205 case BIO_CTRL_PENDING:
206 ret = nbio->Length();
207 break;
208 case BIO_CTRL_DUP:
209 case BIO_CTRL_FLUSH:
210 ret = 1;
211 break;
212 case BIO_CTRL_PUSH:
213 case BIO_CTRL_POP:
214 default:
215 ret = 0;
216 break;
217 }
218 return ret;
219 }
220
221
GetMethod()222 const BIO_METHOD* NodeBIO::GetMethod() {
223 // Static initialization ensures that this is safe to use concurrently.
224 static const BIO_METHOD* method = [&]() {
225 BIO_METHOD* method = BIO_meth_new(BIO_TYPE_MEM, "node.js SSL buffer");
226 BIO_meth_set_write(method, Write);
227 BIO_meth_set_read(method, Read);
228 BIO_meth_set_puts(method, Puts);
229 BIO_meth_set_gets(method, Gets);
230 BIO_meth_set_ctrl(method, Ctrl);
231 BIO_meth_set_create(method, New);
232 BIO_meth_set_destroy(method, Free);
233 return method;
234 }();
235
236 return method;
237 }
238
239
TryMoveReadHead()240 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 while (read_head_->read_pos_ != 0 &&
246 read_head_->read_pos_ == read_head_->write_pos_) {
247 // Reset positions
248 read_head_->read_pos_ = 0;
249 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 if (read_head_ != write_head_)
254 read_head_ = read_head_->next_;
255 }
256 }
257
258
Read(char * out,size_t size)259 size_t NodeBIO::Read(char* out, size_t size) {
260 size_t bytes_read = 0;
261 size_t expected = Length() > size ? size : Length();
262 size_t offset = 0;
263 size_t left = size;
264
265 while (bytes_read < expected) {
266 CHECK_LE(read_head_->read_pos_, read_head_->write_pos_);
267 size_t avail = read_head_->write_pos_ - read_head_->read_pos_;
268 if (avail > left)
269 avail = left;
270
271 // Copy data
272 if (out != nullptr)
273 memcpy(out + offset, read_head_->data_ + read_head_->read_pos_, avail);
274 read_head_->read_pos_ += avail;
275
276 // Move pointers
277 bytes_read += avail;
278 offset += avail;
279 left -= avail;
280
281 TryMoveReadHead();
282 }
283 CHECK_EQ(expected, bytes_read);
284 length_ -= bytes_read;
285
286 // Free all empty buffers, but write_head's child
287 FreeEmpty();
288
289 return bytes_read;
290 }
291
292
FreeEmpty()293 void NodeBIO::FreeEmpty() {
294 if (write_head_ == nullptr)
295 return;
296 Buffer* child = write_head_->next_;
297 if (child == write_head_ || child == read_head_)
298 return;
299 Buffer* cur = child->next_;
300 if (cur == write_head_ || cur == read_head_)
301 return;
302
303 Buffer* prev = child;
304 while (cur != read_head_) {
305 CHECK_NE(cur, write_head_);
306 CHECK_EQ(cur->write_pos_, cur->read_pos_);
307
308 Buffer* next = cur->next_;
309 delete cur;
310 cur = next;
311 }
312 prev->next_ = cur;
313 }
314
315
IndexOf(char delim,size_t limit)316 size_t NodeBIO::IndexOf(char delim, size_t limit) {
317 size_t bytes_read = 0;
318 size_t max = Length() > limit ? limit : Length();
319 size_t left = limit;
320 Buffer* current = read_head_;
321
322 while (bytes_read < max) {
323 CHECK_LE(current->read_pos_, current->write_pos_);
324 size_t avail = current->write_pos_ - current->read_pos_;
325 if (avail > left)
326 avail = left;
327
328 // Walk through data
329 char* tmp = current->data_ + current->read_pos_;
330 size_t off = 0;
331 while (off < avail && *tmp != delim) {
332 off++;
333 tmp++;
334 }
335
336 // Move pointers
337 bytes_read += off;
338 left -= off;
339
340 // Found `delim`
341 if (off != avail) {
342 return bytes_read;
343 }
344
345 // Move to next buffer
346 if (current->read_pos_ + avail == current->len_) {
347 current = current->next_;
348 }
349 }
350 CHECK_EQ(max, bytes_read);
351
352 return max;
353 }
354
355
Write(const char * data,size_t size)356 void NodeBIO::Write(const char* data, size_t size) {
357 size_t offset = 0;
358 size_t left = size;
359
360 // Allocate initial buffer if the ring is empty
361 TryAllocateForWrite(left);
362
363 while (left > 0) {
364 size_t to_write = left;
365 CHECK_LE(write_head_->write_pos_, write_head_->len_);
366 size_t avail = write_head_->len_ - write_head_->write_pos_;
367
368 if (to_write > avail)
369 to_write = avail;
370
371 // Copy data
372 memcpy(write_head_->data_ + write_head_->write_pos_,
373 data + offset,
374 to_write);
375
376 // Move pointers
377 left -= to_write;
378 offset += to_write;
379 length_ += to_write;
380 write_head_->write_pos_ += to_write;
381 CHECK_LE(write_head_->write_pos_, write_head_->len_);
382
383 // Go to next buffer if there still are some bytes to write
384 if (left != 0) {
385 CHECK_EQ(write_head_->write_pos_, write_head_->len_);
386 TryAllocateForWrite(left);
387 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 TryMoveReadHead();
392 }
393 }
394 CHECK_EQ(left, 0);
395 }
396
397
PeekWritable(size_t * size)398 char* NodeBIO::PeekWritable(size_t* size) {
399 TryAllocateForWrite(*size);
400
401 size_t available = write_head_->len_ - write_head_->write_pos_;
402 if (*size == 0 || available <= *size)
403 *size = available;
404
405 return write_head_->data_ + write_head_->write_pos_;
406 }
407
408
Commit(size_t size)409 void NodeBIO::Commit(size_t size) {
410 write_head_->write_pos_ += size;
411 length_ += size;
412 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 TryAllocateForWrite(0);
417 if (write_head_->write_pos_ == write_head_->len_) {
418 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 TryMoveReadHead();
423 }
424 }
425
426
TryAllocateForWrite(size_t hint)427 void NodeBIO::TryAllocateForWrite(size_t hint) {
428 Buffer* w = write_head_;
429 Buffer* r = read_head_;
430 // If write head is full, next buffer is either read head or not empty.
431 if (w == nullptr ||
432 (w->write_pos_ == w->len_ &&
433 (w->next_ == r || w->next_->write_pos_ != 0))) {
434 size_t len = w == nullptr ? initial_ :
435 kThroughputBufferLength;
436 if (len < hint)
437 len = hint;
438
439 // If there is a one time allocation size hint, use it.
440 if (allocate_hint_ > len) {
441 len = allocate_hint_;
442 allocate_hint_ = 0;
443 }
444
445 Buffer* next = new Buffer(env_, len);
446
447 if (w == nullptr) {
448 next->next_ = next;
449 write_head_ = next;
450 read_head_ = next;
451 } else {
452 next->next_ = w->next_;
453 w->next_ = next;
454 }
455 }
456 }
457
458
Reset()459 void NodeBIO::Reset() {
460 if (read_head_ == nullptr)
461 return;
462
463 while (read_head_->read_pos_ != read_head_->write_pos_) {
464 CHECK(read_head_->write_pos_ > read_head_->read_pos_);
465
466 length_ -= read_head_->write_pos_ - read_head_->read_pos_;
467 read_head_->write_pos_ = 0;
468 read_head_->read_pos_ = 0;
469
470 read_head_ = read_head_->next_;
471 }
472 write_head_ = read_head_;
473 CHECK_EQ(length_, 0);
474 }
475
476
~NodeBIO()477 NodeBIO::~NodeBIO() {
478 if (read_head_ == nullptr)
479 return;
480
481 Buffer* current = read_head_;
482 do {
483 Buffer* next = current->next_;
484 delete current;
485 current = next;
486 } while (current != read_head_);
487
488 read_head_ = nullptr;
489 write_head_ = nullptr;
490 }
491
492
FromBIO(BIO * bio)493 NodeBIO* NodeBIO::FromBIO(BIO* bio) {
494 CHECK_NOT_NULL(BIO_get_data(bio));
495 return static_cast<NodeBIO*>(BIO_get_data(bio));
496 }
497
498
499 } // namespace crypto
500 } // namespace node
501