]> sourceware.org Git - systemtap.git/commitdiff
stapdyn: use process_vm_readv/writev when possible
authorJosh Stone <jistone@redhat.com>
Fri, 12 Sep 2014 22:51:10 +0000 (15:51 -0700)
committerJosh Stone <jistone@redhat.com>
Fri, 12 Sep 2014 22:51:10 +0000 (15:51 -0700)
For script deref uses, we were using pread/pwrite on /proc/self/mem.
But thanks to CVE-2012-0056, some kernels have completely disabled
mem_write, so stapdyn guru scripts can't write memory at all.  Our
testsuite failed on at_var_lvalue.exp and deref.exp in this case.

When the process_vm_readv/writev syscalls are available, we can
accomplish the same thing, and don't even need an open fd for it.

runtime/dyninst/copy.c
runtime/dyninst/runtime.h

index 4b284542e827601e0c8f1171a6c6764c94318a7c..24c17e075b85a087d12a2d862abe15db2140a5ef 100644 (file)
 #ifndef _STAPDYN_COPY_C_
 #define _STAPDYN_COPY_C_
 
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/uio.h>
+
 #include "stp_string.c"
 
 
 static int _stp_mem_fd = -1;
 
+
+static inline __must_check ssize_t __self_readv(const struct iovec *lvec,
+                                               unsigned long liovcnt,
+                                               const struct iovec *rvec,
+                                               unsigned long riovcnt)
+{
+#if !__GLIBC_PREREQ(2, 15)
+#ifdef __NR_process_vm_readv
+#define process_vm_readv(...) \
+       syscall(__NR_process_vm_readv, __VA_ARGS__)
+#else
+#define process_vm_readv(...) \
+       ({ (void)lvec; (void)liovcnt; \
+        (void)rvec; (void)riovcnt; \
+        errno = ENOSYS; -1 })
+#endif
+#endif
+       return process_vm_readv(getpid(), lvec, liovcnt, rvec, riovcnt, 0UL);
+}
+
+static inline __must_check ssize_t __self_writev(const struct iovec *lvec,
+                                                unsigned long liovcnt,
+                                                const struct iovec *rvec,
+                                                unsigned long riovcnt)
+{
+#if !__GLIBC_PREREQ(2, 15)
+#ifdef __NR_process_vm_writev
+#define process_vm_writev(...) \
+       syscall(__NR_process_vm_writev, __VA_ARGS__)
+#else
+#define process_vm_writev(...) \
+       ({ (void)lvec; (void)liovcnt; \
+        (void)rvec; (void)riovcnt; \
+        errno = ENOSYS; -1 })
+#endif
+#endif
+       return process_vm_writev(getpid(), lvec, liovcnt, rvec, riovcnt, 0UL);
+}
+
+
+static int _stp_copy_init(void)
+{
+       /* Try a no-op process_vm_readv/writev to make sure they're available,
+        * esp. not ENOSYS, then we don't need to bother /proc/self/mem.  */
+       if ((__self_readv(NULL, 0, NULL, 0) == 0) &&
+                       (__self_writev(NULL, 0, NULL, 0) == 0))
+               return 0;
+
+       _stp_mem_fd = open("/proc/self/mem", O_RDWR /*| O_LARGEFILE*/);
+       if (_stp_mem_fd < 0)
+               return -errno;
+       fcntl(_stp_mem_fd, F_SETFD, FD_CLOEXEC);
+       return 0;
+}
+
+static void _stp_copy_destroy(void)
+{
+       if (_stp_mem_fd >= 0) {
+               close (_stp_mem_fd);
+               _stp_mem_fd = -1;
+       }
+}
+
+
 static inline __must_check long __copy_from_user(void *to,
                const void __user * from, unsigned long n)
 {
        int rc = 0;
 
-       /*
-        * The pread syscall is faster than lseek()/read() (since it
-        * is only one syscall). Also, if we used lseek()/read() we
-        * couldn't use a cached fd - since 2 threads might hit this
-        * code at the same time and the 2nd lseek() might finish
-        * before the 1st read()...
-        */
-       if (pread(_stp_mem_fd, to, n, (off_t)(uintptr_t)from) != n)
-               rc = -EFAULT;
+       if (_stp_mem_fd >= 0) {
+               /* pread is like lseek+read, without racing other threads. */
+               if (pread(_stp_mem_fd, to, n, (off_t)(uintptr_t)from) != n)
+                       rc = -EFAULT;
+       } else {
+               struct iovec lvec = { .iov_base = to, .iov_len = n };
+               struct iovec rvec = { .iov_base = (void *)from, .iov_len = n };
+               if (__self_readv(&lvec, 1, &rvec, 1) != n)
+                       rc = -EFAULT;
+       }
+
        return rc;
 }
 
@@ -38,12 +108,18 @@ static inline __must_check long __copy_to_user(void *to, const void *from,
 {
        int rc = 0;
 
-       /*
-        * The pwrite syscall is faster than lseek()/write() (since it
-        * is only one syscall).
-        */
-       if (pwrite(_stp_mem_fd, from, n, (off_t)(uintptr_t)to) != n)
-               rc = -EFAULT;
+       if (_stp_mem_fd >= 0) {
+               /* pwrite is like lseek+write, without racing other threads. */
+               /* NB: some kernels will refuse to write /proc/self/mem  */
+               if (pwrite(_stp_mem_fd, from, n, (off_t)(uintptr_t)to) != n)
+                       rc = -EFAULT;
+       } else {
+               struct iovec lvec = { .iov_base = (void *)from, .iov_len = n };
+               struct iovec rvec = { .iov_base = to, .iov_len = n };
+               if (__self_writev(&lvec, 1, &rvec, 1) != n)
+                       rc = -EFAULT;
+       }
+
        return rc;
 }
 
index 1ae95a8a2eff1d20e1ed989803dbb1d1ff03de96..55a5faecb2581f80b424c5ca36f61a24146a5e66 100644 (file)
@@ -304,13 +304,7 @@ static void stp_dyninst_ctor(void)
 {
     int rc = 0;
 
-    _stp_mem_fd = open("/proc/self/mem", O_RDWR /*| O_LARGEFILE*/);
-    if (_stp_mem_fd != -1) {
-        fcntl(_stp_mem_fd, F_SETFD, FD_CLOEXEC);
-    }
-    else {
-        rc = -errno;
-    }
+    rc = _stp_copy_init();
 
     if (rc == 0)
         rc = _stp_runtime_contexts_init();
@@ -386,10 +380,7 @@ static void stp_dyninst_dtor(void)
 {
     _stp_print_cleanup();
     _stp_shm_destroy();
-
-    if (_stp_mem_fd != -1) {
-       close (_stp_mem_fd);
-    }
+    _stp_copy_destroy();
 }
 
 #endif /* _STAPDYN_RUNTIME_H_ */
This page took 0.031804 seconds and 5 git commands to generate.