Add serialization for syscall args (XCC)
authorKevin Klues <klueska@cs.berkeley.edu>
Fri, 10 Jul 2015 08:39:38 +0000 (01:39 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Tue, 14 Jul 2015 14:33:16 +0000 (10:33 -0400)
It is useful to be able to serialize data into a single buffer for
passing data to the kernel.  The first use case of this is serializing
the data pointed to by argv and envp along with their pointers.
Currently, we use procinfo to serialize this data and pass it to the
kernel. This is unnecessarily limiting (procinfo is read-only and
limited in size), and there may be other data we would like serialized
in the future. It would be unreasonable to keep expanding procinfo every
time we had a new data structure we wanted to serialize to the kernel.

This commit introduces a new struct serialized_data, which contains
nothing more than a size and an unbounded buffer. Serialization routines
can be written around this type to serialize their input and return a
newly allocated buffer with all of the serialized data contained in it.
All pointers in the data structure must be relative to the base of the
argument buffer itself (rather than absolute pointers).  I've written a
serializer for the argv and envp stuff to pack all of their data into a
single buffer. The plan is to use this new serialized representation to
replace the existing procinfo approach to passing this data into the
kernel (though that will come in a subsequent commit).  More serializers
can be written following this pattern as desired in the future.

I toyed around with the idea of not serializing the argv and envp data
at all, and just passing their pointers directly to the kernel. However,
this approach proved to get increasingly complicated as I started to
reason through what needed to be done to verify all of the user memory
while walking these data structures in the kernel. It's obviously doable
(linux does it this way, for example), but it is unnecessarily
complicated, and likely safer, to just pass the serialized data in.

By serializing the data, we can pass a single pointer and a
length to the kernel (similar to how we do for our path strings), and
the kernel can do a quick check to verify that all of the memory to be
accessed is mapped in and within bounds. I very much like this pattern,
moving forward, for all complicated data structures we may need to pass
to the kernel. We may not always be able to get away with it, but it
makes sense to use it when we can.

In terms of implementing this stuff, I put the actual code in glibc and
the header file in parlib. Ideally I would have put it all in parlib
(since it's not really a part of glibc), but I already know that
sys_exec will need access to these functions, and there may be others in
the future.  The header file belongs where it is though, as this really
is a parlib operation.

As part of this, I also wrote a simple test that demonstrates how the
serialization of argv and envp works. It is called serialize_test.

tests/serialize_test.c [new file with mode: 0644]
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/Makefile
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/Versions
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/serialize.c [new file with mode: 0644]
user/parlib/include/serialize.h [new file with mode: 0644]

diff --git a/tests/serialize_test.c b/tests/serialize_test.c
new file mode 100644 (file)
index 0000000..60c0f5c
--- /dev/null
@@ -0,0 +1,25 @@
+/* Copyright (c) 2014 Google Inc., All Rights Reserved.
+ * Kevin Klues <klueska@google.com>
+ * See LICENSE for details. */
+
+#include <parlib/serialize.h>
+#include <stdint.h>
+#include <stdio.h>
+
+int main(int argc, char **argv, char **envp)
+{
+       struct serialized_data *sd = serialize_argv_envp(argv, envp);
+       size_t *kargc = (size_t*)sd->buf;
+       size_t *kenvc = (size_t*)(sd->buf + sizeof(size_t));
+       char **kargv = (char**)(sd->buf + 2*sizeof(size_t));
+       char **kenvp = (char**)(kargv + *kargc);
+       uintptr_t bufbase = (uintptr_t)(kenvp + *kenvc);
+       printf("argc: %lu\n", *kargc);
+       printf("envc: %lu\n", *kenvc);
+       for (int i = 0; i < *kargc; i++)
+               printf("argv[%d]: %s\n", i, kargv[i] + bufbase);
+       for (int i = 0; i < *kenvc; i++)
+               printf("envp[%d]: %s\n", i, kenvp[i] + bufbase);
+       free_serialized_data(sd);
+       return 0;
+}
index b618b63..19ada0c 100644 (file)
@@ -35,3 +35,8 @@ sysdep_routines += convM2S
 sysdep_routines += convS2M
 endif
 sysdep_headers += fcall.h
 sysdep_routines += convS2M
 endif
 sysdep_headers += fcall.h
+
+# Syscall pack arg routines
+ifeq ($(subdir),stdlib)
+sysdep_routines += serialize
+endif
index cdf331c..b71ebc7 100644 (file)
@@ -33,6 +33,9 @@ libc {
     convD2M;
     sizeD2M;
 
     convD2M;
     sizeD2M;
 
+    serialize_argv_envp;
+    free_serialized_data;
+
     # helper functions from resolv (which we don't build)
     __h_errno_location;
   }
     # helper functions from resolv (which we don't build)
     __h_errno_location;
   }
diff --git a/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/serialize.c b/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/serialize.c
new file mode 100644 (file)
index 0000000..d8c1fd0
--- /dev/null
@@ -0,0 +1,77 @@
+/* Copyright (c) 2015 Google Inc., All Rights Reserved.
+ * Kevin Klues <klueska@google.com>
+ * See LICENSE for details. */
+
+#include <parlib/serialize.h>
+#include <malloc.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct serialized_data* serialize_argv_envp(char* const* argv,
+                                            char* const* envp)
+{
+       size_t bufsize = 0;
+       size_t argc = 0, envc = 0;
+       struct serialized_data *sd;
+
+       /* Count how many args and environment variables we have. */
+       if(argv) while(argv[argc]) argc++;
+       if(envp) while(envp[envc]) envc++;
+
+       /* Reserve space for argc and envc as well as space for the actual
+        * pointers to our arguments and environment variables in a
+        * to-be-allocated buffer ... */
+       bufsize += sizeof(argc) + sizeof(envc);
+       bufsize += ((argc + envc) * sizeof(uintptr_t));
+
+       /* Reserve space in the buffer for each of our arguments (the +1 comes
+        * from the '\0' character) */
+       int arglens[argc];
+       for (int i = 0; i < argc; i++) {
+               arglens[i] = strlen(argv[i]) + 1;
+               bufsize += arglens[i];
+       }
+
+       /* Reserve space in our buffer for each of our environment variables (the
+        * +1 comes from the '\0' character) */
+       int envlens[envc];
+       for (int i = 0; i < envc; i++) {
+               envlens[i] = strlen(envp[i]) + 1;
+               bufsize += envlens[i];
+       }
+
+       /* Allocate an sd struct with enough room for all of our
+        * arguments, environment variables, and their pointers. */
+       sd = calloc(1, sizeof(struct serialized_data) + bufsize);
+       if (!sd)
+               return sd;
+
+       /* Now fill in the buffer!. */
+       size_t *sd_argc = (size_t*)(sd->buf);
+       size_t *sd_envc = (size_t*)(sd->buf + sizeof(size_t));
+       char **ppos = (char**)(sd->buf + 2*sizeof(size_t));
+       char *vpos = (char*)(ppos + argc + envc);
+       char *vpos_base = vpos;
+
+       sd->len = bufsize;
+       *sd_argc = argc;
+       *sd_envc = envc;
+       for (int i = 0; i < argc; i++) {
+               ppos[i] = vpos - vpos_base;
+               memcpy(vpos, argv[i], arglens[i]);
+               vpos += arglens[i];
+       }
+       for(int i = 0; i < envc; i++) {
+               ppos[argc + i] = vpos - vpos_base;
+               memcpy(vpos, envp[i], envlens[i]);
+               vpos += envlens[i];
+       }
+       return sd;
+}
+
+void free_serialized_data(struct serialized_data *sd)
+{
+       free(sd);
+}
+
diff --git a/user/parlib/include/serialize.h b/user/parlib/include/serialize.h
new file mode 100644 (file)
index 0000000..f778b62
--- /dev/null
@@ -0,0 +1,18 @@
+/* Copyright (c) 2015 Google Inc., All Rights Reserved.
+ * Kevin Klues <klueska@google.com>
+ * See LICENSE for details. */
+
+#ifndef PARLIB_SERIALIZE_H
+#define PARLIB_SERIALIZE_H
+
+#include <stddef.h>
+
+struct serialized_data {
+       size_t len;
+       char buf[];
+};
+extern struct serialized_data* serialize_argv_envp(char* const* argv,
+                                                   char* const* envp);
+extern void free_serialized_data(struct serialized_data* sd);
+
+#endif // PARLIB_SERIALIZE_H