File Coverage

secret_buffer_base.c
Criterion Covered Total %
statement 126 145 86.9
branch 74 112 66.0
condition n/a
subroutine n/a
pod n/a
total 200 257 77.8


line stmt bran cond sub pod time code
1             /**********************************************************************************************\
2             * SecretBuffer C API
3             \**********************************************************************************************/
4              
5             /* Given a SV which you expect to be a reference to a blessed object with SecretBuffer
6             * magic, return the secret_buffer struct pointer.
7             * With no flags, this returns NULL is any of the above assumption is not correct.
8             * Specify AUTOCREATE to create a new secret_buffer (and attach with magic) if it is a blessed
9             * object and doesn't have the magic yet.
10             * Specify OR_DIE if you want an exception instead of NULL return value.
11             * Specify UNDEF_OK if you want input C to translate to C even when OR_DIE is
12             * requested.
13             */
14 566           static void *secret_buffer_auto_ctor(pTHX_ SV *owner) {
15 566           secret_buffer *buf= NULL;
16 566           Newxz(buf, 1, secret_buffer);
17 566           buf->wrapper= owner;
18 566           return buf;
19             }
20 5612           secret_buffer* secret_buffer_from_magic(SV *obj, int flags) {
21             dTHX;
22 5612           return (secret_buffer*) secret_buffer_X_from_magic(aTHX_
23             obj, flags,
24             &secret_buffer_magic_vtbl, "secret_buffer",
25             secret_buffer_auto_ctor);
26             }
27              
28             /* Create a new Crypt::SecretBuffer object with a mortal ref and return the secret_buffer.
29             * If ref_out is NULL then the mortal ref remains mortal and the buffer is freed at the next
30             * FREETMPS as your function exits. If you supply a pointer to receive ref_out, you can then
31             * increment the refcount or copy the ref if you want to keep the object.
32             * Always returns a secret_buffer, or croaks on failure.
33             */
34 566           secret_buffer* secret_buffer_new(size_t capacity, SV **ref_out) {
35             dTHX;
36 566           SV *ref= sv_2mortal(newRV_noinc((SV*) newHV()));
37 566           sv_bless(ref, gv_stashpv("Crypt::SecretBuffer", GV_ADD));
38 566           secret_buffer *buf= secret_buffer_from_magic(ref, SECRET_BUFFER_MAGIC_AUTOCREATE);
39 566 100         if (capacity) secret_buffer_alloc_at_least(buf, capacity);
40 566 100         if (ref_out) *ref_out= ref;
41 566           return buf;
42             }
43              
44             /* Reallocate (or free) the buffer of secret_buffer, fully erasing it before deallocation.
45             * If capacity is zero, the buffer will be freed and 'data' pointer set to NULL.
46             * Any other size will allocate exactly that number of bytes, copy any previous bytes,
47             * wipe the old buffer, and free it.
48             * Note that the entire capacity is copied regardless of 'len', to prevent timing attacks from
49             * deducing the exact length of the secret.
50             */
51 1197           void secret_buffer_realloc(secret_buffer *buf, size_t new_capacity) {
52             dTHX;
53 1197 100         if (buf->capacity != new_capacity) {
54 1187 100         if (new_capacity) {
55 626           char *old= buf->data;
56 626           Newxz(buf->data, new_capacity, char);
57 626 100         if (old && buf->capacity) {
    50          
58 65           memcpy(buf->data, old, new_capacity < buf->capacity? new_capacity : buf->capacity);
59 65           secret_buffer_wipe(old, buf->capacity);
60 65           Safefree(old);
61             }
62             } else { /* new capacity is zero, so free the buffer */
63 561 50         if (buf->data && buf->capacity) {
    50          
64 561           secret_buffer_wipe(buf->data, buf->capacity);
65 561           Safefree(buf->data);
66 561           buf->data= NULL;
67             }
68             }
69 1187           buf->capacity= new_capacity;
70 1187 100         if (buf->len > buf->capacity)
71 557           buf->len= buf->capacity;
72            
73             /* If has been exposed as "stringify" sv, update that SV */
74 1187 100         if (buf->stringify_sv) {
75 34           SvPVX(buf->stringify_sv)= buf->data;
76 34           SvCUR(buf->stringify_sv)= buf->len;
77             }
78             }
79 1197           }
80              
81             /* Reallocate the buffer to have at least this many bytes. This is a request for minimum total
82             * capacity, not additional capacity. If the buffer is already large enough, this does nothing.
83             */
84 631           void secret_buffer_alloc_at_least(secret_buffer *buf, size_t min_capacity) {
85 631 100         if (buf->capacity < min_capacity) {
86             /* round up to a multiple of 64 */
87 624           secret_buffer_realloc(buf, (min_capacity + 63) & ~(size_t)63);
88             }
89 631           }
90              
91             /* Set the length of defined data within the buffer.
92             * If it shrinks, the bytes beyond the end get zeroed.
93             * If it grows, the new bytes are zeroes (by virtue of having already cleared the allocation)
94             */
95 1548           void secret_buffer_set_len(secret_buffer *buf, size_t new_len) {
96 1548 100         if (new_len > buf->capacity)
97 573           secret_buffer_alloc_at_least(buf, new_len);
98             /* if it shrinks, need to wipe those bytes. If it grows, the extra is already zeroed */
99 1548 100         if (new_len < buf->len)
100 6           secret_buffer_wipe(buf->data + new_len, buf->capacity - new_len);
101 1548           buf->len= new_len;
102             /* If the stringify scalar has been exposed to perl, update its length */
103 1548 100         if (buf->stringify_sv)
104 102           SvCUR(buf->stringify_sv)= new_len;
105 1548           }
106              
107             /* Return a pointer to some data and the length of that data, using SvPVbyte or equivalent
108             * for SecretBuffer or SecretBuffer::Span objects.
109             */
110 481           const char *secret_buffer_SvPVbyte(SV *thing, STRLEN *len_out) {
111             dTHX;
112             secret_buffer *src_buf;
113             secret_buffer_span *span;
114             // The string is not NUL-terminated, so the user must use this value
115 481 50         if (!len_out)
116 0           croak("len_out is required");
117             // if it is a ref to something that isn't an object (like a scalar-ref)
118             // then load from that instead.
119 481 50         if (thing && SvROK(thing) && !sv_isobject(thing))
    100          
    100          
120 1           thing= SvRV(thing);
121             // NULL or undef represent an empty string
122 481 50         if (!thing || !SvOK(thing)) {
    100          
123 1           *len_out= 0;
124 1           return "";
125             }
126 480 100         else if ((src_buf= secret_buffer_from_magic(thing, 0))) {
127 161           *len_out= src_buf->len;
128 161 50         return src_buf->data? src_buf->data : "";
129             }
130 319 100         else if ((span= secret_buffer_span_from_magic(thing, 0))) {
131 19           SV **buf_field= hv_fetchs(((HV*)SvRV(thing)), "buf", 0);
132 19 50         if (!buf_field || !*buf_field || !(src_buf= secret_buffer_from_magic(*buf_field, 0)))
    50          
    50          
133 0           croak("Span lacks reference to source buffer");
134 19 50         if (span->lim > src_buf->len || span->pos > span->lim)
    50          
135 0           croak("Span references invalid range of source buffer");
136 19           *len_out= span->lim - span->pos;
137 19 50         return src_buf->data? (src_buf->data + span->pos) : "";
138             }
139             else {
140 300           return SvPVbyte(thing, *len_out);
141             }
142             }
143              
144             /* Overwrite the span of the buffer with the contents of the SV, taking into account
145             * whether it might be a scalar-ref, SecretBuffer, or SecretBuffer::Span.
146             */
147 149           void secret_buffer_splice(secret_buffer *buf, size_t ofs, size_t len,
148             const char *replacement, size_t replacement_len
149             ) {
150             dTHX;
151             IV tail_len;
152             const char *splice_pos;
153              
154 149 50         if (ofs > buf->len)
155 0           croak("Attempt to splice beyond end of buffer");
156 149 50         if (ofs + len > buf->len)
157 0           len= buf->len - ofs;
158              
159 149           tail_len= buf->len - (ofs + len);
160 149 100         if (replacement_len > len) /* buffer is growing */
161 143           secret_buffer_set_len(buf, buf->len + (replacement_len - len));
162 149           splice_pos= buf->data + ofs;
163             //warn("splice: buf->data=%p buf->len=%d buf->capacity=%d ofs=%d len=%d replacement=%p replacement_len=%d splice_pos=%p tail_len=%d",
164             // buf->data, (int)buf->len, (int)buf->capacity, (int)ofs, (int)len, replacement, (int)replacement_len, splice_pos, (int)tail_len);
165             /* copy anything beyond the splice to its new location */
166 149 100         if (tail_len)
167 5           Move(splice_pos + len, splice_pos + replacement_len, tail_len, unsigned char);
168             /* copy new data */
169 149 100         if (replacement_len)
170 145           Copy(replacement, splice_pos, replacement_len, unsigned char);
171 149 100         if (replacement_len < len) /* buffer shrank, wipe remainder */
172 3           secret_buffer_set_len(buf, buf->len - len + replacement_len);
173 149           }
174              
175 149           void secret_buffer_splice_sv(secret_buffer *buf, size_t ofs, size_t len, SV *replacement) {
176             STRLEN repl_len;
177 149           const char *repl_str= secret_buffer_SvPVbyte(replacement, &repl_len);
178 149           secret_buffer_splice(buf, ofs, len, repl_str, repl_len);
179 149           }
180              
181             /* This is just exposing the wipe function of this library for general use.
182             * It will be OPENSSL_cleanse if openssl (and headers) were available when this package was
183             * compiled, or a simple 'explicit_bzero' or 'Zero' otherwise.
184             */
185 632           void secret_buffer_wipe(char *buf, size_t len) {
186             #if defined WIN32
187             SecureZeroMemory(buf, len);
188             #elif defined(HAVE_EXPLICIT_BZERO)
189 632           explicit_bzero(buf, len);
190             #else
191             /* this ought to be sufficient anyway because its within an extern function */
192             Zero(buf, len, char);
193             #endif
194 632           }
195              
196 2           IV secret_buffer_append_random(secret_buffer *buf, size_t n, unsigned flags) {
197 2           size_t orig_len= buf->len;
198              
199 2 50         if (!n)
200 0           return 0;
201 2 100         if (buf->capacity < buf->len + n)
202 1           secret_buffer_alloc_at_least(buf, buf->len + n);
203              
204             #ifdef WIN32
205             {
206             HCRYPTPROV hProv;
207              
208             if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
209             croak_with_syserror("CryptAcquireContext failed", GetLastError());
210              
211             if (!CryptGenRandom(hProv, n, buf->data + buf->len)) {
212             DWORD err_id= GetLastError();
213             CryptReleaseContext(hProv, 0);
214             croak_with_syserror("CryptGenRandom failed", err_id);
215             }
216             secret_buffer_set_len(buf, buf->len + n);
217              
218             CryptReleaseContext(hProv, 0);
219             }
220             #else
221             int got;
222             #ifndef HAVE_GETRANDOM
223             int fd= open("/dev/random", O_RDONLY | (flags & SECRET_BUFFER_NONBLOCK? O_NONBLOCK : 0));
224             if (fd < 0) croak("Failed opening /dev/random");
225             #endif
226 4 100         while (n > 0) {
227             #ifdef HAVE_GETRANDOM
228 2           got= getrandom(buf->data + buf->len, n, GRND_RANDOM | (flags & SECRET_BUFFER_NONBLOCK? GRND_NONBLOCK : 0));
229             #else
230             got= read(fd, buf->data + buf->len, n);
231             #endif
232 2 50         if (got <= 0) {
233 0 0         if (got < 0 && errno == EINTR)
    0          
234 0           continue; /* keep trying */
235 0 0         if ((flags & SECRET_BUFFER_NONBLOCK) && (got == 0 || errno == EWOULDBLOCK || errno == EAGAIN))
    0          
    0          
    0          
236             break; /* user requested a single try */
237             #ifdef HAVE_GETRANDOM
238 0           croak_with_syserror("getrandom", errno);
239             #else
240             croak_with_syserror("read /dev/random", errno);
241             #endif
242             }
243 2           secret_buffer_set_len(buf, buf->len + got);
244 2           n -= got;
245             }
246             #endif
247 2           return (IV)(buf->len - orig_len);
248             }
249              
250             /* Approximate perl's sysread implementation (esp for Win32) on our secret buffer.
251             * Also warn if Perl has buffered data for this handle.
252             */
253 198           IV secret_buffer_append_sysread(secret_buffer *buf, PerlIO *stream, size_t count) {
254             dTHX;
255 198           int stream_fd= PerlIO_fileno(stream);
256 198 50         if (stream_fd < 0)
257 0           croak("Handle has no system file descriptor (fileno)");
258 198 50         if (PerlIO_get_cnt(stream))
259 0           warn("Handle has buffered input, ignored by sysread");
260             /* reserve buffer space */
261 198 100         if (buf->capacity < buf->len + count)
262 19           secret_buffer_alloc_at_least(buf, buf->len + count);
263             #ifdef WIN32
264             {
265             HANDLE hFile = (HANDLE)_get_osfhandle(stream_fd);
266             DWORD bytes_read;
267             if (hFile == INVALID_HANDLE_VALUE)
268             croak("Handle has no system file descriptor");
269             if (!ReadFile(hFile, buf->data + buf->len, (DWORD)count, &bytes_read, NULL)) {
270             /* POSIX only gives EPIPE to the writer, the reader gets a 0 read to indicate EOF.
271             * Win32 gives this error to the reader instead of a 0 read. */
272             if (GetLastError() == ERROR_BROKEN_PIPE)
273             return 0;
274             return -1;
275             }
276             secret_buffer_set_len(buf, buf->len + bytes_read);
277             return bytes_read;
278             }
279             #else
280             {
281 198           int ret= read(stream_fd, buf->data + buf->len, count);
282 198 100         if (ret > 0)
283 189           secret_buffer_set_len(buf, buf->len + ret);
284 198           return ret;
285             }
286             #endif
287             }
288              
289             /* Approximate perl's syswrite implementation (esp for Win32) on our secret buffer.
290             * Also flush any perl buffer, first.
291             */
292 4           IV secret_buffer_syswrite(secret_buffer *buf, PerlIO *stream, IV offset, IV count) {
293             dTHX;
294 4           int stream_fd= PerlIO_fileno(stream);
295             /* translate negative offset or negative count, in the manner of substr */
296 4           offset= normalize_offset(offset, buf->len);
297 4           count= normalize_offset(count, buf->len - offset);
298             /* flush any buffered data already on the handle */
299 4           PerlIO_flush(stream);
300              
301 4 50         if (stream_fd < 0)
302 0           croak("Handle has no system file descriptor (fileno)");
303             #ifdef WIN32
304             {
305             HANDLE hFile = (HANDLE)_get_osfhandle(stream_fd);
306             DWORD wrote;
307             if (hFile == INVALID_HANDLE_VALUE)
308             croak("Handle has no system file descriptor");
309             if (!WriteFile(hFile, buf->data + offset, (DWORD)count, &wrote, NULL))
310             return -1;
311             return wrote;
312             }
313             #else
314 4           return write(stream_fd, buf->data + offset, count);
315             #endif
316             }
317              
318             /* Read any existing buffered data from Perl, and sysread otherwise.
319             * There's not much point if the handle is virtual, because the secret will be
320             * elsewhere in memory, but this gives the user flexibility.
321             */
322 213           IV secret_buffer_append_read(secret_buffer *buf, PerlIO *stream, size_t count) {
323             dTHX;
324 213           int stream_fd= PerlIO_fileno(stream);
325 213           SSize_t n_buffered= PerlIO_get_cnt(stream);
326             char *perlbuffer;
327             /* if it's a virtual handle, or if perl has already buffered some data, then read from PerlIO */
328 213 100         if (stream_fd < 0 || n_buffered > 0) {
    100          
329 21 50         if (n_buffered <= 0) {
330 0 0         if (PerlIO_fill(stream) < 0)
331 0           return -1;
332 0           n_buffered= PerlIO_get_cnt(stream);
333 0 0         if (n_buffered <= 0)
334 0           return 0;
335             }
336             /* Read from Perl's buffer, then wipe it if safe to do so */
337 21           perlbuffer= PerlIO_get_ptr(stream);
338 21 50         if (count > n_buffered)
339 0           count= n_buffered;
340             {
341 21           size_t off = buf->len;
342 21           secret_buffer_set_len(buf, buf->len + count);
343 21           memcpy(buf->data + off, perlbuffer, count);
344             }
345             /* secret_buffer_wipe(perlbuffer, count); could be a scalar, or read-only constant, or shared string table :-( */
346 21           PerlIO_set_ptrcnt(stream, perlbuffer+count, n_buffered-count);
347 21           return count;
348             }
349             /* Its a real descriptor with no buffer, so sysread */
350             else
351 192           return secret_buffer_append_sysread(buf, stream, count);
352             }
353