sshbuf.c 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /* $OpenBSD: sshbuf.c,v 1.15 2020/02/26 13:40:09 jsg Exp $ */
  2. /*
  3. * Copyright (c) 2011 Damien Miller
  4. *
  5. * Permission to use, copy, modify, and distribute this software for any
  6. * purpose with or without fee is hereby granted, provided that the above
  7. * copyright notice and this permission notice appear in all copies.
  8. *
  9. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. */
  17. #define SSHBUF_INTERNAL
  18. #include "includes.h"
  19. #include <sys/types.h>
  20. #include <signal.h>
  21. #include <stdlib.h>
  22. #include <stdio.h>
  23. #include <string.h>
  24. #include "ssherr.h"
  25. #include "sshbuf.h"
  26. #include "misc.h"
  27. static inline int
  28. sshbuf_check_sanity(const struct sshbuf *buf)
  29. {
  30. SSHBUF_TELL("sanity");
  31. if (__predict_false(buf == NULL ||
  32. (!buf->readonly && buf->d != buf->cd) ||
  33. buf->refcount < 1 || buf->refcount > SSHBUF_REFS_MAX ||
  34. buf->cd == NULL ||
  35. buf->max_size > SSHBUF_SIZE_MAX ||
  36. buf->alloc > buf->max_size ||
  37. buf->size > buf->alloc ||
  38. buf->off > buf->size)) {
  39. /* Do not try to recover from corrupted buffer internals */
  40. SSHBUF_DBG(("SSH_ERR_INTERNAL_ERROR"));
  41. ssh_signal(SIGSEGV, SIG_DFL);
  42. raise(SIGSEGV);
  43. return SSH_ERR_INTERNAL_ERROR;
  44. }
  45. return 0;
  46. }
  47. static void
  48. sshbuf_maybe_pack(struct sshbuf *buf, int force)
  49. {
  50. SSHBUF_DBG(("force %d", force));
  51. SSHBUF_TELL("pre-pack");
  52. if (buf->off == 0 || buf->readonly || buf->refcount > 1)
  53. return;
  54. if (force ||
  55. (buf->off >= SSHBUF_PACK_MIN && buf->off >= buf->size / 2)) {
  56. memmove(buf->d, buf->d + buf->off, buf->size - buf->off);
  57. buf->size -= buf->off;
  58. buf->off = 0;
  59. SSHBUF_TELL("packed");
  60. }
  61. }
  62. struct sshbuf *
  63. sshbuf_new(void)
  64. {
  65. struct sshbuf *ret;
  66. if ((ret = calloc(sizeof(*ret), 1)) == NULL)
  67. return NULL;
  68. ret->alloc = SSHBUF_SIZE_INIT;
  69. ret->max_size = SSHBUF_SIZE_MAX;
  70. ret->readonly = 0;
  71. ret->refcount = 1;
  72. ret->parent = NULL;
  73. if ((ret->cd = ret->d = calloc(1, ret->alloc)) == NULL) {
  74. free(ret);
  75. return NULL;
  76. }
  77. return ret;
  78. }
  79. struct sshbuf *
  80. sshbuf_from(const void *blob, size_t len)
  81. {
  82. struct sshbuf *ret;
  83. if (blob == NULL || len > SSHBUF_SIZE_MAX ||
  84. (ret = calloc(sizeof(*ret), 1)) == NULL)
  85. return NULL;
  86. ret->alloc = ret->size = ret->max_size = len;
  87. ret->readonly = 1;
  88. ret->refcount = 1;
  89. ret->parent = NULL;
  90. ret->cd = blob;
  91. ret->d = NULL;
  92. return ret;
  93. }
  94. int
  95. sshbuf_set_parent(struct sshbuf *child, struct sshbuf *parent)
  96. {
  97. int r;
  98. if ((r = sshbuf_check_sanity(child)) != 0 ||
  99. (r = sshbuf_check_sanity(parent)) != 0)
  100. return r;
  101. child->parent = parent;
  102. child->parent->refcount++;
  103. return 0;
  104. }
  105. struct sshbuf *
  106. sshbuf_fromb(struct sshbuf *buf)
  107. {
  108. struct sshbuf *ret;
  109. if (sshbuf_check_sanity(buf) != 0)
  110. return NULL;
  111. if ((ret = sshbuf_from(sshbuf_ptr(buf), sshbuf_len(buf))) == NULL)
  112. return NULL;
  113. if (sshbuf_set_parent(ret, buf) != 0) {
  114. sshbuf_free(ret);
  115. return NULL;
  116. }
  117. return ret;
  118. }
  119. void
  120. sshbuf_free(struct sshbuf *buf)
  121. {
  122. if (buf == NULL)
  123. return;
  124. /*
  125. * The following will leak on insane buffers, but this is the safest
  126. * course of action - an invalid pointer or already-freed pointer may
  127. * have been passed to us and continuing to scribble over memory would
  128. * be bad.
  129. */
  130. if (sshbuf_check_sanity(buf) != 0)
  131. return;
  132. /*
  133. * If we are a parent with still-extant children, then don't free just
  134. * yet. The last child's call to sshbuf_free should decrement our
  135. * refcount to 0 and trigger the actual free.
  136. */
  137. buf->refcount--;
  138. if (buf->refcount > 0)
  139. return;
  140. /*
  141. * If we are a child, the free our parent to decrement its reference
  142. * count and possibly free it.
  143. */
  144. sshbuf_free(buf->parent);
  145. buf->parent = NULL;
  146. if (!buf->readonly) {
  147. explicit_bzero(buf->d, buf->alloc);
  148. free(buf->d);
  149. }
  150. freezero(buf, sizeof(*buf));
  151. }
  152. void
  153. sshbuf_reset(struct sshbuf *buf)
  154. {
  155. u_char *d;
  156. if (buf->readonly || buf->refcount > 1) {
  157. /* Nonsensical. Just make buffer appear empty */
  158. buf->off = buf->size;
  159. return;
  160. }
  161. (void) sshbuf_check_sanity(buf);
  162. buf->off = buf->size = 0;
  163. if (buf->alloc != SSHBUF_SIZE_INIT) {
  164. if ((d = recallocarray(buf->d, buf->alloc, SSHBUF_SIZE_INIT,
  165. 1)) != NULL) {
  166. buf->cd = buf->d = d;
  167. buf->alloc = SSHBUF_SIZE_INIT;
  168. }
  169. }
  170. explicit_bzero(buf->d, SSHBUF_SIZE_INIT);
  171. }
  172. size_t
  173. sshbuf_max_size(const struct sshbuf *buf)
  174. {
  175. return buf->max_size;
  176. }
  177. size_t
  178. sshbuf_alloc(const struct sshbuf *buf)
  179. {
  180. return buf->alloc;
  181. }
  182. const struct sshbuf *
  183. sshbuf_parent(const struct sshbuf *buf)
  184. {
  185. return buf->parent;
  186. }
  187. u_int
  188. sshbuf_refcount(const struct sshbuf *buf)
  189. {
  190. return buf->refcount;
  191. }
  192. int
  193. sshbuf_set_max_size(struct sshbuf *buf, size_t max_size)
  194. {
  195. size_t rlen;
  196. u_char *dp;
  197. int r;
  198. SSHBUF_DBG(("set max buf = %p len = %zu", buf, max_size));
  199. if ((r = sshbuf_check_sanity(buf)) != 0)
  200. return r;
  201. if (max_size == buf->max_size)
  202. return 0;
  203. if (buf->readonly || buf->refcount > 1)
  204. return SSH_ERR_BUFFER_READ_ONLY;
  205. if (max_size > SSHBUF_SIZE_MAX)
  206. return SSH_ERR_NO_BUFFER_SPACE;
  207. /* pack and realloc if necessary */
  208. sshbuf_maybe_pack(buf, max_size < buf->size);
  209. if (max_size < buf->alloc && max_size > buf->size) {
  210. if (buf->size < SSHBUF_SIZE_INIT)
  211. rlen = SSHBUF_SIZE_INIT;
  212. else
  213. rlen = ROUNDUP(buf->size, SSHBUF_SIZE_INC);
  214. if (rlen > max_size)
  215. rlen = max_size;
  216. SSHBUF_DBG(("new alloc = %zu", rlen));
  217. if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL)
  218. return SSH_ERR_ALLOC_FAIL;
  219. buf->cd = buf->d = dp;
  220. buf->alloc = rlen;
  221. }
  222. SSHBUF_TELL("new-max");
  223. if (max_size < buf->alloc)
  224. return SSH_ERR_NO_BUFFER_SPACE;
  225. buf->max_size = max_size;
  226. return 0;
  227. }
  228. size_t
  229. sshbuf_len(const struct sshbuf *buf)
  230. {
  231. if (sshbuf_check_sanity(buf) != 0)
  232. return 0;
  233. return buf->size - buf->off;
  234. }
  235. size_t
  236. sshbuf_avail(const struct sshbuf *buf)
  237. {
  238. if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
  239. return 0;
  240. return buf->max_size - (buf->size - buf->off);
  241. }
  242. const u_char *
  243. sshbuf_ptr(const struct sshbuf *buf)
  244. {
  245. if (sshbuf_check_sanity(buf) != 0)
  246. return NULL;
  247. return buf->cd + buf->off;
  248. }
  249. u_char *
  250. sshbuf_mutable_ptr(const struct sshbuf *buf)
  251. {
  252. if (sshbuf_check_sanity(buf) != 0 || buf->readonly || buf->refcount > 1)
  253. return NULL;
  254. return buf->d + buf->off;
  255. }
  256. int
  257. sshbuf_check_reserve(const struct sshbuf *buf, size_t len)
  258. {
  259. int r;
  260. if ((r = sshbuf_check_sanity(buf)) != 0)
  261. return r;
  262. if (buf->readonly || buf->refcount > 1)
  263. return SSH_ERR_BUFFER_READ_ONLY;
  264. SSHBUF_TELL("check");
  265. /* Check that len is reasonable and that max_size + available < len */
  266. if (len > buf->max_size || buf->max_size - len < buf->size - buf->off)
  267. return SSH_ERR_NO_BUFFER_SPACE;
  268. return 0;
  269. }
  270. int
  271. sshbuf_allocate(struct sshbuf *buf, size_t len)
  272. {
  273. size_t rlen, need;
  274. u_char *dp;
  275. int r;
  276. SSHBUF_DBG(("allocate buf = %p len = %zu", buf, len));
  277. if ((r = sshbuf_check_reserve(buf, len)) != 0)
  278. return r;
  279. /*
  280. * If the requested allocation appended would push us past max_size
  281. * then pack the buffer, zeroing buf->off.
  282. */
  283. sshbuf_maybe_pack(buf, buf->size + len > buf->max_size);
  284. SSHBUF_TELL("allocate");
  285. if (len + buf->size <= buf->alloc)
  286. return 0; /* already have it. */
  287. /*
  288. * Prefer to alloc in SSHBUF_SIZE_INC units, but
  289. * allocate less if doing so would overflow max_size.
  290. */
  291. need = len + buf->size - buf->alloc;
  292. rlen = ROUNDUP(buf->alloc + need, SSHBUF_SIZE_INC);
  293. SSHBUF_DBG(("need %zu initial rlen %zu", need, rlen));
  294. if (rlen > buf->max_size)
  295. rlen = buf->alloc + need;
  296. SSHBUF_DBG(("adjusted rlen %zu", rlen));
  297. if ((dp = recallocarray(buf->d, buf->alloc, rlen, 1)) == NULL) {
  298. SSHBUF_DBG(("realloc fail"));
  299. return SSH_ERR_ALLOC_FAIL;
  300. }
  301. buf->alloc = rlen;
  302. buf->cd = buf->d = dp;
  303. if ((r = sshbuf_check_reserve(buf, len)) < 0) {
  304. /* shouldn't fail */
  305. return r;
  306. }
  307. SSHBUF_TELL("done");
  308. return 0;
  309. }
  310. int
  311. sshbuf_reserve(struct sshbuf *buf, size_t len, u_char **dpp)
  312. {
  313. u_char *dp;
  314. int r;
  315. if (dpp != NULL)
  316. *dpp = NULL;
  317. SSHBUF_DBG(("reserve buf = %p len = %zu", buf, len));
  318. if ((r = sshbuf_allocate(buf, len)) != 0)
  319. return r;
  320. dp = buf->d + buf->size;
  321. buf->size += len;
  322. if (dpp != NULL)
  323. *dpp = dp;
  324. return 0;
  325. }
  326. int
  327. sshbuf_consume(struct sshbuf *buf, size_t len)
  328. {
  329. int r;
  330. SSHBUF_DBG(("len = %zu", len));
  331. if ((r = sshbuf_check_sanity(buf)) != 0)
  332. return r;
  333. if (len == 0)
  334. return 0;
  335. if (len > sshbuf_len(buf))
  336. return SSH_ERR_MESSAGE_INCOMPLETE;
  337. buf->off += len;
  338. /* deal with empty buffer */
  339. if (buf->off == buf->size)
  340. buf->off = buf->size = 0;
  341. SSHBUF_TELL("done");
  342. return 0;
  343. }
  344. int
  345. sshbuf_consume_end(struct sshbuf *buf, size_t len)
  346. {
  347. int r;
  348. SSHBUF_DBG(("len = %zu", len));
  349. if ((r = sshbuf_check_sanity(buf)) != 0)
  350. return r;
  351. if (len == 0)
  352. return 0;
  353. if (len > sshbuf_len(buf))
  354. return SSH_ERR_MESSAGE_INCOMPLETE;
  355. buf->size -= len;
  356. SSHBUF_TELL("done");
  357. return 0;
  358. }