aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/interceptor/kernel_encode.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/interceptor/kernel_encode.c')
-rw-r--r--drivers/interceptor/kernel_encode.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/drivers/interceptor/kernel_encode.c b/drivers/interceptor/kernel_encode.c
new file mode 100644
index 0000000..f13694d
--- /dev/null
+++ b/drivers/interceptor/kernel_encode.c
@@ -0,0 +1,580 @@
+/* Netfilter Driver for IPSec VPN Client
+ *
+ * Copyright(c) 2012 Samsung Electronics
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * kernel_encode.c
+ *
+ * Encode/decode API implementation for kernel space.
+ *
+ */
+
+#include "sshincludes.h"
+#include "sshgetput.h"
+#include "sshencode.h"
+
+/* Encodes data into the buffer. Returns the number of bytes added, or
+ 0 if the buffer is too small. The data to be encoded is specified by the
+ variable-length argument list. Each element must start with a
+ SshEncodingFormat type, be followed by arguments of the appropriate
+ type, and the list must end with SSH_FORMAT_END. */
+size_t ssh_encode_array_va(unsigned char *buf, size_t bufsize, va_list ap)
+{
+ SshEncodingFormat format;
+ unsigned int intvalue;
+ SshUInt16 u16;
+ SshUInt32 u32;
+ SshUInt64 u64;
+ size_t len, offset;
+ Boolean b;
+ const unsigned char *p;
+
+ offset = 0;
+ for (;;)
+ {
+ format = va_arg(ap, SshEncodingFormat);
+ switch (format)
+ {
+ case SSH_FORMAT_UINT32_STR:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ if (bufsize - offset < 4 + len)
+ return 0;
+ SSH_PUT_32BIT(buf + offset, len);
+ memcpy(buf + offset + 4, p, len);
+ offset += 4 + len;
+ break;
+
+ case SSH_FORMAT_BOOLEAN:
+ b = va_arg(ap, Boolean);
+ if (bufsize - offset < 1)
+ return 0;
+ buf[offset++] = (unsigned char)b;
+ break;
+
+ case SSH_FORMAT_UINT32:
+ u32 = va_arg(ap, SshUInt32);
+ if (bufsize - offset < 4)
+ return 0;
+ SSH_PUT_32BIT(buf + offset, u32);
+ offset += 4;
+ break;
+
+ case SSH_FORMAT_UINT16:
+ intvalue = va_arg(ap, unsigned int);
+ u16 = (SshUInt16) intvalue;
+ if (bufsize - offset < 2)
+ return 0;
+ SSH_PUT_16BIT(buf + offset, u16);
+ offset += 2;
+ break;
+
+ case SSH_FORMAT_CHAR:
+ intvalue = va_arg(ap, unsigned int);
+ if (bufsize - offset < 1)
+ return 0;
+ buf[offset++] = (unsigned char)intvalue;
+ break;
+
+ case SSH_FORMAT_DATA:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ if (bufsize - offset < len)
+ return 0;
+ memcpy(buf + offset, p, len);
+ offset += len;
+ break;
+
+ case SSH_FORMAT_UINT64:
+ u64 = va_arg(ap, SshUInt64);
+ if (bufsize - offset < 8)
+ return 0;
+ SSH_PUT_64BIT(buf + offset, u64);
+ offset += 8;
+ break;
+
+ case SSH_FORMAT_SPECIAL:
+ {
+ SshEncodeDatum fn;
+ void *datum;
+ size_t space, size;
+
+ fn = va_arg(ap, SshEncodeDatum);
+ datum = va_arg(ap, void *);
+
+ space = bufsize - offset;
+ size = (*fn)(buf, space, datum);
+ if (size > space)
+ return 0;
+
+ offset += size;
+ }
+ break;
+
+ case SSH_FORMAT_END:
+ /* Return the number of bytes added. */
+ return offset;
+
+ default:
+ SSH_FATAL("ssh_encode_array_va: invalid format code %d "
+ "(check arguments and SSH_FORMAT_END)",
+ (int)format);
+ }
+ }
+}
+
+/* Appends data at the end of the buffer as specified by the variable-length
+ argument list. Each element must start with a SshEncodingFormat type,
+ be followed by arguments of the appropriate type, and the list must end
+ with SSH_FORMAT_END. This returns the number of bytes added to the
+ buffer, or 0 if the buffer is too small. */
+size_t ssh_encode_array(unsigned char *buf, size_t bufsize, ...)
+{
+ size_t bytes;
+ va_list ap;
+
+ va_start(ap, bufsize);
+ bytes = ssh_encode_array_va(buf, bufsize, ap);
+ va_end(ap);
+ return bytes;
+}
+
+/* Doubles the size of a buffer. Copies the contents from the old one
+ to the new one and frees the old one. Returns new buffer size.
+ If realloc fails, frees the old buffer and returns 0. */
+size_t ssh_encode_array_enlarge_buffer(unsigned char **buf, size_t bufsize)
+{
+ unsigned int newsize;
+ unsigned char *newbuf;
+
+ SSH_ASSERT(buf != NULL);
+
+ newsize = bufsize * 2;
+ SSH_ASSERT(newsize < 10000000);
+
+ if (newsize == 0)
+ newsize = 100;
+
+ newbuf = ssh_realloc(*buf, bufsize, newsize);
+ if (newbuf == NULL)
+ {
+ ssh_free(*buf);
+ return 0;
+ }
+ *buf = newbuf;
+
+ return newsize;
+}
+
+/* Encodes the given data. Returns the length of encoded data in bytes, and
+ if `buf_return' is non-NULL, it is set to a memory area allocated by
+ ssh_malloc that contains the data. The caller should free the data when
+ no longer needed.
+
+ Duplicates functionality in ssh_encode_array_va, could combine
+ some. Note that cycling va_list with va_arg is not supported in all
+ environments, so repeatedly trying ssh_encode_array_va with bigger
+ buffers is not possible. */
+size_t ssh_encode_array_alloc_va(unsigned char **buf_return, va_list ap)
+{
+ size_t bufsize;
+ unsigned char *buf = NULL;
+ SshEncodingFormat format;
+ unsigned int intvalue;
+ SshUInt16 u16;
+ SshUInt32 u32;
+ SshUInt64 u64;
+ size_t len, offset;
+ Boolean b;
+ const unsigned char *p;
+
+ /* Prepare return value in case of later failure. */
+ if (buf_return != NULL)
+ *buf_return = NULL;
+
+ /* Allocate new buffer. */
+ bufsize = ssh_encode_array_enlarge_buffer(&buf, 0);
+
+ offset = 0;
+ for (;;)
+ {
+ format = va_arg(ap, SshEncodingFormat);
+ switch (format)
+ {
+ case SSH_FORMAT_UINT32_STR:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ while (bufsize - offset < 4 + len)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_32BIT(buf + offset, len);
+ memcpy(buf + offset + 4, p, len);
+ offset += 4 + len;
+ break;
+
+ case SSH_FORMAT_BOOLEAN:
+ b = va_arg(ap, Boolean);
+ while (bufsize - offset < 1)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ buf[offset++] = (unsigned char)b;
+ break;
+
+ case SSH_FORMAT_UINT32:
+ u32 = va_arg(ap, SshUInt32);
+ while (bufsize - offset < 4)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_32BIT(buf + offset, u32);
+ offset += 4;
+ break;
+
+ case SSH_FORMAT_UINT16:
+ intvalue = va_arg(ap, unsigned int);
+ u16 = (SshUInt16) intvalue;
+ while (bufsize - offset < 2)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_16BIT(buf + offset, u16);
+ offset += 2;
+ break;
+
+ case SSH_FORMAT_CHAR:
+ intvalue = va_arg(ap, unsigned int);
+ while (bufsize - offset < 1)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ buf[offset++] = (unsigned char)intvalue;
+ break;
+
+ case SSH_FORMAT_DATA:
+ p = va_arg(ap, unsigned char *);
+ len = va_arg(ap, size_t);
+ while (bufsize - offset < len)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ memcpy(buf + offset, p, len);
+ offset += len;
+ break;
+
+ case SSH_FORMAT_UINT64:
+ u64 = va_arg(ap, SshUInt64);
+ while (bufsize - offset < 8)
+ {
+ if (!(bufsize = ssh_encode_array_enlarge_buffer(&buf, bufsize)))
+ return 0;
+ }
+ SSH_PUT_64BIT(buf + offset, u64);
+ offset += 8;
+ break;
+
+ case SSH_FORMAT_END:
+ if (buf_return != NULL)
+ *buf_return = buf;
+ else
+ /* They only want to know the size of the buffer */
+ ssh_free(buf);
+
+ /* Return the number of bytes added. */
+ return offset;
+
+ default:
+ SSH_FATAL("ssh_encode_array_alloc_va: invalid format code %d "
+ "(check arguments and SSH_FORMAT_END)",
+ (int)format);
+ }
+ }
+}
+
+/* Encodes the given data. Returns the length of encoded data in bytes, and
+ if `buf_return' is non-NULL, it is set to a memory area allocated by
+ ssh_malloc that contains the data. The caller should free the data when
+ no longer needed. It is an error to call this with an argument list that
+ would result in zero bytes being encoded. */
+size_t ssh_encode_array_alloc(unsigned char **buf_return, ...)
+{
+ size_t bytes;
+ va_list ap;
+
+ va_start(ap, buf_return);
+ bytes = ssh_encode_array_alloc_va(buf_return, ap);
+ va_end(ap);
+
+ return bytes;
+}
+
+/* Allocates a buffer of the given size with ssh_malloc. However,
+ the buffer is also recorded in *num_allocs_p and *allocs_p, so that
+ they can all be easily freed later if necessary. */
+static unsigned char *ssh_decode_array_alloc(unsigned int *num_allocs_p,
+ unsigned char ***allocsp,
+ size_t size)
+{
+ unsigned char *p;
+
+ /* Check if we need to enlarge the pointer array. We enlarge it in chunks
+ of 16 pointers. */
+ if (*num_allocs_p == 0)
+ *allocsp = NULL;
+
+ /* This will also match *num_allocs_p == 0, and it is valid to pass
+ NULL to krealloc, so this works. */
+ if (*num_allocs_p % 16 == 0)
+ {
+ unsigned char ** nallocsp;
+
+ nallocsp = ssh_realloc(*allocsp,
+ *num_allocs_p
+ * sizeof(unsigned char *),
+ (*num_allocs_p + 16)
+ * sizeof(unsigned char *));
+
+ /* If we fail in allocation, return NULL but leave *allocsp intact. */
+ if (!nallocsp)
+ return NULL;
+
+ *allocsp = nallocsp;
+ }
+
+ /* Allocate the memory block. */
+ if (!(p = ssh_malloc(size)))
+ return NULL;
+
+ /* Store it in the array. */
+ (*allocsp)[*num_allocs_p] = p;
+ (*num_allocs_p)++;
+
+ return p;
+}
+
+/* Decodes data from the given byte array as specified by the
+ variable-length argument list. If all specified arguments could be
+ successfully parsed, returns the number of bytes parsed (any
+ remaining data can be parsed by first skipping this many bytes).
+ If parsing any element results in an error, this returns 0 (and
+ frees any already allocated data). Zero is also returned if the
+ specified length would be exceeded. */
+size_t ssh_decode_array_va(const unsigned char *buf, size_t len,
+ va_list ap)
+{
+ SshEncodingFormat format;
+ unsigned long longvalue;
+ SshUInt16 *u16p;
+ SshUInt32 *u32p;
+ SshUInt64 *u64p;
+ Boolean *bp;
+ size_t size, *sizep;
+ unsigned int *uip;
+ unsigned char *p, **pp;
+ const unsigned char **cpp;
+ size_t offset;
+ unsigned int i, num_allocs;
+ unsigned char **allocs = NULL;
+
+ offset = 0;
+ num_allocs = 0;
+
+ for (;;)
+ {
+ /* Get the next format code. */
+ format = va_arg(ap, SshEncodingFormat);
+ switch (format)
+ {
+
+ case SSH_FORMAT_UINT32_STR:
+ /* Get length and data pointers. */
+ pp = va_arg(ap, unsigned char **);
+ sizep = va_arg(ap, size_t *);
+
+ /* Check if the length of the string is there. */
+ if (len - offset < 4)
+ goto fail;
+
+ /* Get the length of the string. */
+ longvalue = SSH_GET_32BIT(buf + offset);
+ offset += 4;
+
+ /* Check that the string is all in the buffer. */
+ if (longvalue > len - offset)
+ goto fail;
+
+ /* Store length if requested. */
+ if (sizep != NULL)
+ *sizep = longvalue;
+
+ /* Retrieve the data if requested. */
+ if (pp != NULL)
+ {
+ *pp = ssh_decode_array_alloc(&num_allocs, &allocs,
+ (size_t)longvalue + 1);
+
+ if (!*pp)
+ goto fail;
+
+ memcpy(*pp, buf + offset, (size_t)longvalue);
+ (*pp)[longvalue] = '\0';
+ }
+
+ /* Consume the data. */
+ offset += longvalue;
+ break;
+
+ case SSH_FORMAT_UINT32_STR_NOCOPY:
+
+ /* Get length and data pointers. */
+ cpp = va_arg(ap, const unsigned char **);
+ sizep = va_arg(ap, size_t *);
+
+ /* Decode string length and skip the length. */
+
+ if (len - offset < 4)
+ goto fail;
+
+ longvalue = SSH_GET_32BIT(buf + offset);
+ offset += 4;
+
+ /* Check that the string is all in the buffer. */
+ if (longvalue > len - offset)
+ goto fail;
+
+ /* Store length if requested. */
+ if (sizep != NULL)
+ *sizep = longvalue;
+
+ /* Retrieve the data if requested. */
+ if (cpp != NULL)
+ *cpp = buf + offset;
+
+ /* Consume the data. */
+ offset += longvalue;
+ break;
+
+
+ case SSH_FORMAT_BOOLEAN:
+ bp = va_arg(ap, Boolean *);
+ if (len - offset < 1)
+ goto fail;
+ if (bp != NULL)
+ *bp = buf[offset] != 0;
+ offset++;
+ break;
+
+ case SSH_FORMAT_UINT32:
+ u32p = va_arg(ap, SshUInt32 *);
+ if (len - offset < 4)
+ goto fail;
+ if (u32p)
+ *u32p = SSH_GET_32BIT(buf + offset);
+ offset += 4;
+ break;
+
+ case SSH_FORMAT_UINT16:
+ u16p = va_arg(ap, SshUInt16 *);
+ if (len - offset < 2)
+ goto fail;
+ if (u16p)
+ *u16p = SSH_GET_16BIT(buf + offset);
+ offset += 2;
+ break;
+
+ case SSH_FORMAT_CHAR:
+ uip = va_arg(ap, unsigned int *);
+ if (len - offset < 1)
+ goto fail;
+ if (uip)
+ *uip = buf[offset];
+ offset++;
+ break;
+
+ case SSH_FORMAT_DATA:
+ p = va_arg(ap, unsigned char *);
+ size = va_arg(ap, size_t);
+ if (len - offset < size)
+ goto fail;
+ if (p)
+ memcpy(p, buf + offset, size);
+ offset += size;
+ break;
+
+ case SSH_FORMAT_UINT64:
+ u64p = va_arg(ap, SshUInt64 *);
+ if (len - offset < 8)
+ goto fail;
+ if (u64p)
+ *u64p = SSH_GET_64BIT(buf + offset);
+ offset += 8;
+ break;
+
+ case SSH_FORMAT_SPECIAL:
+ {
+ SshDecodeDatum fn;
+ void **datump;
+
+ fn = va_arg(ap, SshDecodeDatum);
+ datump = va_arg(ap, void **);
+ size = (*fn)(buf + offset, len - offset, datump);
+ if (size > len - offset)
+ goto fail;
+ offset += size;
+ }
+ break;
+
+ case SSH_FORMAT_END:
+ /* Free the allocs array. */
+ if (num_allocs > 0)
+ ssh_free(allocs);
+ /* Return the number of bytes consumed. */
+ return offset;
+
+ default:
+ SSH_FATAL("ssh_decode_array_va: invalid format code %d "
+ "(check arguments and SSH_FORMAT_END)",
+ (int)format);
+ }
+ }
+
+fail:
+ /* An error was encountered. Free all allocated memory and return zero. */
+ for (i = 0; i < num_allocs; i++)
+ ssh_free(allocs[i]);
+ if (i > 0)
+ ssh_free(allocs);
+ return 0;
+}
+
+/* Decodes data from the given byte array as specified by the
+ variable-length argument list. If all specified arguments could be
+ successfully parsed, returns the number of bytes parsed (any
+ remaining data can be parsed by first skipping this many bytes).
+ If parsing any element results in an error, this returns 0 (and
+ frees any already allocates data). Zero is also returned if the
+ specified length would be exceeded. */
+size_t ssh_decode_array(const unsigned char *buf, size_t len, ...)
+{
+ va_list ap;
+ size_t bytes;
+
+ va_start(ap, len);
+ bytes = ssh_decode_array_va(buf, len, ap);
+ va_end(ap);
+
+ return bytes;
+}