Sanitize vcoreid from untrusted sources
[akaros.git] / kern / src / umem.c
index a974328..5863ffe 100644 (file)
  * to involve some form of pinning (TODO), and that global static needs to go. */
 
 #include <ros/common.h>
+#include <arch/uaccess.h>
 #include <umem.h>
 #include <process.h>
 #include <error.h>
 #include <kmalloc.h>
 #include <assert.h>
 #include <pmap.h>
+#include <smp.h>
 
-/**
- * @brief Global variable used to store erroneous virtual addresses as the
- *        result of a failed user_mem_check().
- *
- * zra: What if two checks fail at the same time? Maybe this should be per-cpu?
- *
- */
-static void *DANGEROUS RACY user_mem_check_addr;
-
-/**
- * @brief Check that an environment is allowed to access the range of memory
- * [va, va+len) with permissions 'perm | PTE_P'.
- *
- * Normally 'perm' will contain PTE_U at least, but this is not required.  The
- * function get_va_perms only checks for PTE_U, PTE_W, and PTE_P.  It won't
- * check for things like PTE_PS, PTE_A, etc.
- * 'va' and 'len' need not be page-aligned;
- *
- * A user program can access a virtual address if:
- *     -# the address is below ULIM
- *     -# the page table gives it permission.  
- *
- * If there is an error, 'user_mem_check_addr' is set to the first
- * erroneous virtual address.
- *
- * @param p    the process associated with the user program trying to access
- *             the virtual address range
- * @param va   the first virtual address in the range
- * @param len  the length of the virtual address range
- * @param perm the permissions the user is trying to access the virtual address 
- *             range with
- *
- * @return VA a pointer of type COUNT(len) to the address range
- * @return NULL trying to access this range of virtual addresses is not allowed
- */
-void *user_mem_check(struct proc *p, const void *DANGEROUS va, size_t len,
-                     int perm)
+static int string_copy_from_user(char *dst, const char *src)
 {
-       if (len == 0) {
-               warn("Called user_mem_check with a len of 0. Don't do that. Returning NULL");
-               return NULL;
-       }
-       
-       // TODO - will need to sort this out wrt page faulting / PTE_P
-       // also could be issues with sleeping and waking up to find pages
-       // are unmapped, though i think the lab ignores this since the 
-       // kernel is uninterruptible
-       void *DANGEROUS start, *DANGEROUS end;
-       size_t num_pages, i;
-       int page_perms = 0;
-
-       perm |= PTE_P;
-       start = ROUNDDOWN((void*DANGEROUS)va, PGSIZE);
-       end = ROUNDUP((void*DANGEROUS)va + len, PGSIZE);
-       if (start >= end) {
-               warn("Blimey!  Wrap around in VM range calculation!");  
-               return NULL;
-       }
-       num_pages = LA2PPN(end - start);
-       for (i = 0; i < num_pages; i++, start += PGSIZE) {
-               page_perms = get_va_perms(p->env_pgdir, start);
-               // ensures the bits we want on are turned on.  if not, error out
-               if ((page_perms & perm) != perm) {
-                       if (i == 0)
-                               user_mem_check_addr = (void*DANGEROUS)va;
-                       else
-                               user_mem_check_addr = start;
-                       return NULL;
-               }
-       }
-       // this should never be needed, since the perms should catch it
-       if ((uintptr_t)end > ULIM) {
-               warn ("I suck - Bug in user permission mappings!");
-               return NULL;
+       int error;
+       const char *top = src + valid_user_rbytes_from(src);
+
+       for (;; dst++, src++) {
+               if (unlikely(src >= top))
+                       return -EFAULT;
+               error = __get_user(dst, src, 1);
+               if (unlikely(error))
+                       return error;
+               if (unlikely(!*dst))
+                       break;
        }
-       return (void *COUNT(len))TC(va);
+
+       return 0;
 }
 
-/**
- * @brief Checks that process 'p' is allowed to access the range
- * of memory [va, va+len) with permissions 'perm | PTE_U'. Destroy 
- * process 'p' if the assertion fails.
- *
- * This function is identical to user_mem_assert() except that it has a side
- * affect of destroying the process 'p' if the memory check fails.
- *
- * @param p    the process associated with the user program trying to access
- *             the virtual address range
- * @param va   the first virtual address in the range
- * @param len  the length of the virtual address range
- * @param perm the permissions the user is trying to access the virtual address 
- *             range with
- *
- * @return VA a pointer of type COUNT(len) to the address range
- * @return NULL trying to access this range of virtual addresses is not allowed
- *              process 'p' is destroyed
- */
-void *user_mem_assert(struct proc *p, const void *DANGEROUS va, size_t len,
-                       int perm)
+static int string_copy_to_user(char *dst, const char *src)
 {
-       if (len == 0) {
-               warn("Called user_mem_assert with a len of 0. Don't do that. Returning NULL");
-               return NULL;
-       }
-       
-       void *COUNT(len) res = user_mem_check(p, va, len, perm | PTE_USER_RO);
-       if (!res) {
-               cprintf("[%08x] user_mem_check assertion failure for "
-                       "va %08x\n", p->pid, user_mem_check_addr);
-               proc_destroy(p);        // may not return
-        return NULL;
+       int error;
+       char *top = dst + valid_user_rwbytes_from(dst);
+
+       for (;; dst++, src++) {
+               if (unlikely(dst >= top))
+                       return -EFAULT;
+               error = __put_user(dst, src, 1);
+               if (unlikely(error))
+                       return error;
+               if (unlikely(!*src))
+                       break;
        }
-    return res;
+
+       return 0;
 }
 
-/**
- * @brief Copies data from a user buffer to a kernel buffer.
- * 
- * @param p    the process associated with the user program
- *             from which the buffer is being copied
- * @param dest the destination address of the kernel buffer
- * @param va   the address of the userspace buffer from which we are copying
- * @param len  the length of the userspace buffer
- *
- * @return ESUCCESS on success
- * @return -EFAULT  the page assocaited with 'va' is not present, the user 
- *                  lacks the proper permissions, or there was an invalid 'va'
- */
-int memcpy_from_user(struct proc *p, void *dest, const void *DANGEROUS va,
-                     size_t len)
+int strcpy_from_user(struct proc *p, char *dst, const char *src)
 {
-       const void *DANGEROUS start, *DANGEROUS end;
-       size_t num_pages, i;
-       pte_t *pte;
-       uintptr_t perm = PTE_P | PTE_USER_RO;
-       size_t bytes_copied = 0;
+       uintptr_t prev = switch_to(p);
+       int error = string_copy_from_user(dst, src);
 
-       static_assert(ULIM % PGSIZE == 0 && ULIM != 0); // prevent wrap-around
+       switch_back(p, prev);
 
-       start = ROUNDDOWN(va, PGSIZE);
-       end = ROUNDUP(va + len, PGSIZE);
+       return error;
+}
 
-       if (start >= (void*SNT)ULIM || end > (void*SNT)ULIM)
-               return -EFAULT;
+int strcpy_to_user(struct proc *p, char *dst, const char *src)
+{
+       uintptr_t prev = switch_to(p);
+       int error = string_copy_to_user(dst, src);
 
-       num_pages = LA2PPN(end - start);
-       for (i = 0; i < num_pages; i++) {
-               pte = pgdir_walk(p->env_pgdir, start + i * PGSIZE, 0);
-               if (!pte)
-                       return -EFAULT;
-               if ((*pte & PTE_P) && (*pte & PTE_USER_RO) != PTE_USER_RO)
-                       return -EFAULT;
-               if (!(*pte & PTE_P))
-                       if (handle_page_fault(p, (uintptr_t)start + i * PGSIZE, PROT_READ))
-                               return -EFAULT;
-
-               void *kpage = KADDR(PTE_ADDR(*pte));
-               const void *src_start = i > 0 ? kpage : kpage + (va - start);
-               void *dst_start = dest + bytes_copied;
-               size_t copy_len = PGSIZE;
-               if (i == 0)
-                       copy_len -= va - start;
-               if (i == num_pages-1)
-                       copy_len -= end - (va + len);
-
-               memcpy(dst_start, src_start, copy_len);
-               bytes_copied += copy_len;
-       }
-       assert(bytes_copied == len);
-       return 0;
+       switch_back(p, prev);
+
+       return error;
 }
 
-/* Same as above, but sets errno */
-int memcpy_from_user_errno(struct proc *p, void *dst, const void *src, int len)
+int memcpy_from_user(struct proc *p, void *dest, const void *va, size_t len)
 {
-       if (memcpy_from_user(p, dst, src, len)) {
-               set_errno(current_tf, EINVAL);
-               return -1;
-       }
-       return 0;
+       uintptr_t prev = switch_to(p);
+       int error = copy_from_user(dest, va, len);
+
+       switch_back(p, prev);
+
+       return error;
 }
 
-/**
- * @brief Copies data to a user buffer from a kernel buffer.
- * 
- * @param p    the process associated with the user program
- *             to which the buffer is being copied
- * @param dest the destination address of the user buffer
- * @param va   the address of the kernel buffer from which we are copying
- * @param len  the length of the user buffer
- *
- * @return ESUCCESS on success
- * @return -EFAULT  the page assocaited with 'va' is not present, the user 
- *                  lacks the proper permissions, or there was an invalid 'va'
- */
-int memcpy_to_user(struct proc *p, void *va, const void *src, size_t len)
+int memcpy_to_user(struct proc *p, void *dest, const void *src, size_t len)
 {
-       const void *DANGEROUS start, *DANGEROUS end;
-       size_t num_pages, i;
-       pte_t *pte;
-       uintptr_t perm = PTE_P | PTE_USER_RW;
-       size_t bytes_copied = 0;
+       uintptr_t prev = switch_to(p);
+       int error = copy_to_user(dest, src, len);
 
-       static_assert(ULIM % PGSIZE == 0 && ULIM != 0); // prevent wrap-around
+       switch_back(p, prev);
 
-       start = ROUNDDOWN(va, PGSIZE);
-       end = ROUNDUP(va + len, PGSIZE);
+       return error;
+}
 
-       if (start >= (void*SNT)ULIM || end > (void*SNT)ULIM)
-               return -EFAULT;
+/* Same as above, but sets errno */
+int memcpy_from_user_errno(struct proc *p, void *dst, const void *src, int len)
+{
+       int error = memcpy_from_user(p, dst, src, len);
 
-       num_pages = LA2PPN(end - start);
-       for (i = 0; i < num_pages; i++) {
-               pte = pgdir_walk(p->env_pgdir, start + i * PGSIZE, 0);
-               if (!pte)
-                       return -EFAULT;
-               if ((*pte & PTE_P) && (*pte & PTE_USER_RW) != PTE_USER_RW)
-                       return -EFAULT;
-               if (!(*pte & PTE_P))
-                       if (handle_page_fault(p, (uintptr_t)start + i * PGSIZE, PROT_WRITE))
-                               return -EFAULT;
-               void *kpage = KADDR(PTE_ADDR(*pte));
-               void *dst_start = i > 0 ? kpage : kpage + (va - start);
-               const void *src_start = src + bytes_copied;
-               size_t copy_len = PGSIZE;
-               if (i == 0)
-                       copy_len -= va - start;
-               if (i == num_pages - 1)
-                       copy_len -= end - (va + len);
-               memcpy(dst_start, src_start, copy_len);
-               bytes_copied += copy_len;
-       }
-       assert(bytes_copied == len);
-       return 0;
+       if (unlikely(error < 0))
+               set_errno(-error);
+
+       return error;
 }
 
 /* Same as above, but sets errno */
 int memcpy_to_user_errno(struct proc *p, void *dst, const void *src, int len)
 {
-       if (memcpy_to_user(p, dst, src, len)) {
-               set_errno(current_tf, EINVAL);
-               return -1;
-       }
-       return 0;
+       int error = memcpy_to_user(p, dst, src, len);
+
+       if (unlikely(error < 0))
+               set_errno(-error);
+
+       return error;
+}
+
+/* Helpers for FSs that don't care if they copy to the user or the kernel.
+ *
+ * TODO: (KFOP) Probably shouldn't do this.  Either memcpy directly, or split
+ * out the is_user_r(w)addr from copy_{to,from}_user().  Or throw from the fault
+ * handler.  Right now, we ignore the ret/errors completely. */
+int memcpy_to_safe(void *dst, const void *src, size_t amt)
+{
+       int error = 0;
+
+       if (!is_ktask(per_cpu_info[core_id()].cur_kthread))
+               error = memcpy_to_user(current, dst, src, amt);
+       else
+               memcpy(dst, src, amt);
+       return error;
+}
+
+int memcpy_from_safe(void *dst, const void *src, size_t amt)
+{
+       int error = 0;
+
+       if (!is_ktask(per_cpu_info[core_id()].cur_kthread))
+               error = memcpy_from_user(current, dst, src, amt);
+       else
+               memcpy(dst, src, amt);
+       return error;
 }
 
 /* Creates a buffer (kmalloc) and safely copies into it from va.  Can return an
@@ -266,11 +147,12 @@ int memcpy_to_user_errno(struct proc *p, void *dst, const void *src, int len)
 void *user_memdup(struct proc *p, const void *va, int len)
 {
        void* kva = NULL;
+
        if (len < 0 || (kva = kmalloc(len, 0)) == NULL)
                return ERR_PTR(-ENOMEM);
        if (memcpy_from_user(p, kva, va, len)) {
                kfree(kva);
-               return ERR_PTR(-EINVAL);
+               return ERR_PTR(-EFAULT);
        }
        return kva;
 }
@@ -278,8 +160,9 @@ void *user_memdup(struct proc *p, const void *va, int len)
 void *user_memdup_errno(struct proc *p, const void *va, int len)
 {
        void *kva = user_memdup(p, va, len);
+
        if (IS_ERR(kva)) {
-               set_errno(current_tf, -PTR_ERR(kva));
+               set_errno(-PTR_ERR(kva));
                return NULL;
        }
        return kva;
@@ -298,6 +181,7 @@ void user_memdup_free(struct proc *p, void *va)
 char *user_strdup(struct proc *p, const char *u_string, size_t strlen)
 {
        char *k_string = user_memdup(p, u_string, strlen + 1);
+
        if (!IS_ERR(k_string))
                k_string[strlen] = '\0';
        return k_string;
@@ -308,7 +192,7 @@ char *user_strdup_errno(struct proc *p, const char *u_string, size_t strlen)
 {
        void *k_string = user_strdup(p, u_string, strlen);
        if (IS_ERR(k_string)) {
-               set_errno(current_tf, -PTR_ERR(k_string));
+               set_errno(-PTR_ERR(k_string));
                return NULL;
        }
        return k_string;
@@ -317,7 +201,74 @@ char *user_strdup_errno(struct proc *p, const char *u_string, size_t strlen)
 void *kmalloc_errno(int len)
 {
        void *kva = NULL;
+
        if (len < 0 || (kva = kmalloc(len, 0)) == NULL)
-               set_errno(current_tf, ENOMEM);
+               set_errno(ENOMEM);
        return kva;
 }
+
+/* Returns true if uva and kva both resolve to the same phys addr.  If uva is
+ * unmapped, it will return FALSE.  This is probably what you want, since after
+ * all uva isn't kva. */
+bool uva_is_kva(struct proc *p, void *uva, void *kva)
+{
+       struct page *u_page;
+       assert(kva);                            /* catch bugs */
+
+       /* Check offsets first */
+       if (PGOFF(uva) != PGOFF(kva))
+               return FALSE;
+       /* Check to see if it is the same physical page */
+       u_page = page_lookup(p->env_pgdir, uva, 0);
+       if (!u_page)
+               return FALSE;
+       return (kva2page(kva) == u_page) ? TRUE : FALSE;
+}
+
+/* Given a proc and a user virtual address, gives us the KVA.  Useful for
+ * debugging.  Returns 0 if the page is unmapped (page lookup fails).  This
+ * doesn't play nice with Jumbo pages. */
+uintptr_t uva2kva(struct proc *p, void *uva, size_t len, int prot)
+{
+       struct page *u_page;
+       uintptr_t offset = PGOFF(uva);
+
+       if (!p)
+               return 0;
+       if (prot & PROT_WRITE) {
+               if (!is_user_rwaddr(uva, len))
+                       return 0;
+       } else {
+               if (!is_user_raddr(uva, len))
+                       return 0;
+       }
+       u_page = page_lookup(p->env_pgdir, uva, 0);
+       if (!u_page)
+               return 0;
+       return (uintptr_t)page2kva(u_page) + offset;
+}
+
+/* Helper, copies a pathname from the process into the kernel.  Returns a string
+ * on success, which you must free with free_path.  Returns 0 on failure and
+ * sets errno. */
+char *copy_in_path(struct proc *p, const char *path, size_t path_l)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       char *t_path;
+
+       /* PATH_MAX includes the \0 */
+       if (path_l > PATH_MAX) {
+               set_errno(ENAMETOOLONG);
+               return 0;
+       }
+       t_path = user_strdup_errno(p, path, path_l);
+       if (!t_path)
+               return 0;
+       return t_path;
+}
+
+/* Helper, frees a path that was allocated with copy_in_path. */
+void free_path(struct proc *p, char *t_path)
+{
+       user_memdup_free(p, t_path);
+}