Add test for patching a system call instruction

This tests patching a specific, fixed instruction sequence, whereas
the existing tests just test whatever is in the version of glibc on
the system.

Change library.cc to make the patching code easier to test: Split out
a patchSystemCallsInRange() method, since the existing methods assume
they are operating on a whole dynamically-loaded ELF object.  Change
maps_ to be per-instance so that creating Library objects with
different Maps objects works.

This doesn't try to cover the various corner cases in library.cc yet.

BUG=http://code.google.com/p/seccompsandbox/issues/detail?id=17
TEST=test_patching_syscall

Review URL: http://codereview.chromium.org/8596009

git-svn-id: http://seccompsandbox.googlecode.com/svn/trunk@177 55e79e8e-603c-11de-8c10-5fe6993ea61f
diff --git a/library.cc b/library.cc
index 0842a47..5845943 100644
--- a/library.cc
+++ b/library.cc
@@ -59,7 +59,6 @@
 
 namespace playground {
 
-Maps* Library::maps_;
 char* Library::__kernel_vsyscall;
 char* Library::__kernel_sigreturn;
 char* Library::__kernel_rt_sigreturn;
@@ -70,7 +69,8 @@
       asr_offset_(0),
       vsys_offset_(0),
       image_(0),
-      image_size_(0) {
+      image_size_(0),
+      maps_(NULL) {
 }
 
 Library::~Library() {
@@ -1014,6 +1014,17 @@
   const Elf_Shdr& shdr = iter->second.second;
   char* start = reinterpret_cast<char *>(shdr.sh_addr + asr_offset_);
   char* stop = start + shdr.sh_size;
+  patchSystemCallsInRange(start, stop, &extraSpace, &extraLength);
+
+  // Mark our scratch space as write-protected and executable.
+  if (extraSpace) {
+    Sandbox::SysCalls sys;
+    sys.mprotect(extraSpace, 4096, PROT_READ|PROT_EXEC);
+  }
+}
+
+void Library::patchSystemCallsInRange(char* start, char* stop,
+                                      char** extraSpace, int* extraLength) {
   char* func = start;
   int nopcount = 0;
   bool has_syscall = false;
@@ -1047,7 +1058,7 @@
           // Our quick scan of the function found a potential system call.
           // Do a more thorough scan, now.
           patchSystemCallsInFunction(maps_, isVDSO_ ? vsys_offset_ : 0, func,
-                                     ptr, &extraSpace, &extraLength);
+                                     ptr, extraSpace, extraLength);
         }
         func = ptr;
       }
@@ -1060,13 +1071,7 @@
     // Patch any remaining system calls that were in the last function before
     // the loop terminated.
     patchSystemCallsInFunction(maps_, isVDSO_ ? vsys_offset_ : 0, func, stop,
-                               &extraSpace, &extraLength);
-  }
-
-  // Mark our scratch space as write-protected and executable.
-  if (extraSpace) {
-    Sandbox::SysCalls sys;
-    sys.mprotect(extraSpace, 4096, PROT_READ|PROT_EXEC);
+                               extraSpace, extraLength);
   }
 }
 
diff --git a/library.h b/library.h
index c90a9e6..5f0d781 100644
--- a/library.h
+++ b/library.h
@@ -119,6 +119,8 @@
   const Elf_Shdr* getSection(const string& section);
   void makeWritable(bool state) const;
   void patchSystemCalls();
+  void patchSystemCallsInRange(char* start, char* stop,
+                               char** extraSpace, int* extraLength);
   bool isVDSO() const { return isVDSO_; }
 
  protected:
@@ -174,7 +176,7 @@
   SymbolTable     symbols_;
   char*           image_;
   size_t          image_size_;
-  static Maps*    maps_;
+  Maps*           maps_;
   static char*    __kernel_vsyscall;
   static char*    __kernel_sigreturn;
   static char*    __kernel_rt_sigreturn;
diff --git a/makefile b/makefile
index 9ce2759..041e875 100644
--- a/makefile
+++ b/makefile
@@ -13,6 +13,8 @@
 TEST_MODS := \
         tests/clone_test_helper \
         tests/test_runner \
+        tests/test_patching \
+        tests/test_patching_input \
         tests/test_syscalls
 OBJS64 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o64/')
 OBJS32 := $(shell echo ${MODS} | xargs -n 1 | sed -e 's/$$/.o32/')
diff --git a/seccomp.gyp b/seccomp.gyp
index 72c4ade..baabeea 100644
--- a/seccomp.gyp
+++ b/seccomp.gyp
@@ -65,6 +65,8 @@
         'reference_trusted_thread.cc',
         'tests/clone_test_helper.S',
         'tests/test_runner.cc',
+        'tests/test_patching.cc',
+        'tests/test_patching_input.S',
         'tests/test_syscalls.cc',
       ],
       'include_dirs': [
diff --git a/tests/test_patching.cc b/tests/test_patching.cc
new file mode 100644
index 0000000..bb1babb
--- /dev/null
+++ b/tests/test_patching.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fcntl.h>
+
+#include "library.h"
+#include "sandbox.h"
+#include "test_runner.h"
+
+
+extern "C" int my_getpid(void);
+extern char my_getpid_end[];
+
+void patch_range(char *start, char *end) {
+  int maps_fd;
+  CHECK_SUCCEEDS((maps_fd = open("/proc/self/maps", O_RDONLY, 0)) >= 0);
+  playground::Maps maps(maps_fd);
+  playground::Library library;
+  library.setLibraryInfo(&maps);
+  char *extra_space = NULL;
+  int extra_size = 0;
+  char *page_start = (char *) ((uintptr_t) start & ~(getpagesize() - 1));
+  CHECK_SUCCEEDS(mprotect(page_start, end - page_start,
+                          PROT_READ | PROT_WRITE | PROT_EXEC) == 0);
+  library.patchSystemCallsInRange(start, end, &extra_space, &extra_size);
+  CHECK_SUCCEEDS(close(maps_fd) == 0);
+}
+
+TEST(test_patching_syscall) {
+  int pid = getpid();
+  CHECK(my_getpid() == pid);
+  char *func = (char *) my_getpid;
+  char *func_end = my_getpid_end;
+  patch_range(func, func_end);
+#if defined(__x86_64__)
+  CHECK(func[0] == '\xe9'); // e9 XX XX XX XX   jmp X
+  CHECK(func[5] == '\x90'); // 90               nop
+  CHECK(func[6] == '\x90'); // 90               nop
+  CHECK(func[7] == '\xc3'); // c3               ret (unmodified)
+#elif defined(__i386__)
+  CHECK(func[0] == '\x68'); // 68 XX XX XX XX   push $X
+  CHECK(func[5] == '\xc3'); // c3               ret
+  CHECK(func[6] == '\x90'); // 90               nop
+  CHECK(func[7] == '\xc3'); // c3               ret (unmodified)
+#else
+# error Unsupported target platform
+#endif
+  StartSeccompSandbox();
+  CHECK(my_getpid() == pid);
+}
diff --git a/tests/test_patching_input.S b/tests/test_patching_input.S
new file mode 100644
index 0000000..3e7126d
--- /dev/null
+++ b/tests/test_patching_input.S
@@ -0,0 +1,26 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <asm/unistd.h>
+
+
+        // This performs a system call directly so that we can test
+        // patching this instruction sequence.
+
+        .global my_getpid
+        .global my_getpid_end
+my_getpid:
+        mov $__NR_getpid, %eax
+#if defined(__x86_64__)
+        syscall
+#elif defined(__i386__)
+        int $0x80
+#else
+# error Unsupported target platform
+#endif
+        ret
+my_getpid_end:
+
+        // Tell Linux not to disable no-execute protection for the process.
+        .section .note.GNU-stack,"",@progbits
diff --git a/tests/test_runner.h b/tests/test_runner.h
index 8269027..209c00a 100644
--- a/tests/test_runner.h
+++ b/tests/test_runner.h
@@ -5,6 +5,9 @@
 #ifndef TEST_RUNNER_H__
 #define TEST_RUNNER_H__
 
+#include <errno.h>
+#include <stdio.h>
+
 
 void intend_exit_status(int val, bool is_signal);
 void add_test_case(const char *test_name, void (*test_func)());