|
14 | 14 | */ |
15 | 15 |
|
16 | 16 | #include <string.h> |
| 17 | +#include <sys/param.h> |
17 | 18 |
|
18 | 19 | #include "stuffer/s2n_stuffer.h" |
19 | 20 | #include "utils/s2n_mem.h" |
@@ -206,3 +207,73 @@ int s2n_stuffer_init_ro_from_string(struct s2n_stuffer *stuffer, uint8_t *data, |
206 | 207 |
|
207 | 208 | return S2N_SUCCESS; |
208 | 209 | } |
| 210 | + |
| 211 | +/* If we call va_start or va_copy there MUST be a matching call to va_end, |
| 212 | + * so we should use DEFER_CLEANUP with our va_lists. |
| 213 | + * Unfortunately, some environments implement va_list in ways that don't |
| 214 | + * act as expected when passed by reference. For example, because va_end is |
| 215 | + * a macro it may expect va_list to be an array (maybe to call sizeof), |
| 216 | + * but passing va_list by reference will cause it to decay to a pointer instead. |
| 217 | + * To avoid any surprises, just wrap the va_list in our own struct. |
| 218 | + */ |
| 219 | +struct s2n_va_list { |
| 220 | + va_list va_list; |
| 221 | +}; |
| 222 | + |
| 223 | +static void s2n_va_list_cleanup(struct s2n_va_list *list) |
| 224 | +{ |
| 225 | + if (list) { |
| 226 | + va_end(list->va_list); |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +int s2n_stuffer_vprintf(struct s2n_stuffer *stuffer, const char *format, va_list vargs_in) |
| 231 | +{ |
| 232 | + POSIX_PRECONDITION(s2n_stuffer_validate(stuffer)); |
| 233 | + POSIX_ENSURE_REF(format); |
| 234 | + |
| 235 | + /* vsnprintf consumes the va_list, so copy it first */ |
| 236 | + DEFER_CLEANUP(struct s2n_va_list vargs_1 = { 0 }, s2n_va_list_cleanup); |
| 237 | + va_copy(vargs_1.va_list, vargs_in); |
| 238 | + |
| 239 | + /* The first call to vsnprintf calculates the size of the formatted string. |
| 240 | + * str_len does not include the one byte vsnprintf requires for a trailing '\0', |
| 241 | + * so we need one more byte. |
| 242 | + */ |
| 243 | + int str_len = vsnprintf(NULL, 0, format, vargs_1.va_list); |
| 244 | + POSIX_ENSURE_GTE(str_len, 0); |
| 245 | + int mem_size = str_len + 1; |
| 246 | + |
| 247 | + /* 'tainted' indicates that pointers to the contents of the stuffer exist, |
| 248 | + * so resizing / reallocated the stuffer will invalidate those pointers. |
| 249 | + * However, we do no resize the stuffer in this method after creating `str` |
| 250 | + * and `str` does not live beyond this method, so ignore `str` for the |
| 251 | + * purposes of tracking 'tainted'. |
| 252 | + */ |
| 253 | + bool previously_tainted = stuffer->tainted; |
| 254 | + char *str = s2n_stuffer_raw_write(stuffer, mem_size); |
| 255 | + stuffer->tainted = previously_tainted; |
| 256 | + POSIX_GUARD_PTR(str); |
| 257 | + |
| 258 | + /* vsnprintf again consumes the va_list, so copy it first */ |
| 259 | + DEFER_CLEANUP(struct s2n_va_list vargs_2 = { 0 }, s2n_va_list_cleanup); |
| 260 | + va_copy(vargs_2.va_list, vargs_in); |
| 261 | + |
| 262 | + /* This time, vsnprintf actually writes the formatted string */ |
| 263 | + int written = vsnprintf(str, mem_size, format, vargs_2.va_list); |
| 264 | + POSIX_ENSURE_GTE(written, 0); |
| 265 | + |
| 266 | + /* We don't actually use c-strings, so erase the final '\0' */ |
| 267 | + POSIX_GUARD(s2n_stuffer_wipe_n(stuffer, 1)); |
| 268 | + |
| 269 | + POSIX_POSTCONDITION(s2n_stuffer_validate(stuffer)); |
| 270 | + return S2N_SUCCESS; |
| 271 | +} |
| 272 | + |
| 273 | +int s2n_stuffer_printf(struct s2n_stuffer *stuffer, const char *format, ...) |
| 274 | +{ |
| 275 | + DEFER_CLEANUP(struct s2n_va_list vargs = { 0 }, s2n_va_list_cleanup); |
| 276 | + va_start(vargs.va_list, format); |
| 277 | + POSIX_GUARD(s2n_stuffer_vprintf(stuffer, format, vargs.va_list)); |
| 278 | + return S2N_SUCCESS; |
| 279 | +} |
0 commit comments