]> git.saurik.com Git - apple/xnu.git/blobdiff - osfmk/i386/user_ldt.c
xnu-6153.101.6.tar.gz
[apple/xnu.git] / osfmk / i386 / user_ldt.c
index 35dd2cef786250a813c63b76ef44612c0a45efd7..14f6816282293a49865b8ae96ae8400d4758143d 100644 (file)
@@ -2,7 +2,7 @@
  * Copyright (c) 2000-2009 Apple Inc. All rights reserved.
  *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
- * 
+ *
  * This file contains Original Code and/or Modifications of Original Code
  * as defined in and that are subject to the Apple Public Source License
  * Version 2.0 (the 'License'). You may not use this file except in
  * unlawful or unlicensed copies of an Apple operating system, or to
  * circumvent, violate, or enable the circumvention or violation of, any
  * terms of an Apple operating system software license agreement.
- * 
+ *
  * Please obtain a copy of the License at
  * http://www.opensource.apple.com/apsl/ and read it before using this file.
- * 
+ *
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
  * Please see the License for the specific language governing rights and
  * limitations under the License.
- * 
+ *
  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
  */
 /*
  * @OSF_COPYRIGHT@
  */
-/* 
+/*
  * Mach Operating System
  * Copyright (c) 1991 Carnegie Mellon University
  * All Rights Reserved.
- * 
+ *
  * Permission to use, copy, modify and distribute this software and its
  * documentation is hereby granted, provided that both the copyright
  * notice and this permission notice appear in all copies of the
  * software, derivative works or modified versions, and any portions
  * thereof, and that both notices appear in supporting documentation.
- * 
+ *
  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
- * 
+ *
  * Carnegie Mellon requests users of this software to return to
- * 
+ *
  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
  *  School of Computer Science
  *  Carnegie Mellon University
  *  Pittsburgh PA 15213-3890
- * 
- * any improvements or extensions that they make and grant Carnegie Mellon 
+ *
+ * any improvements or extensions that they make and grant Carnegie Mellon
  * the rights to redistribute these changes.
  */
 
 #include <sys/errno.h>
 
 static void user_ldt_set_action(void *);
+static int i386_set_ldt_impl(uint32_t *retval, uint64_t start_sel, uint64_t descs,
+    uint64_t num_sels);
+static int i386_get_ldt_impl(uint32_t *retval, uint64_t start_sel, uint64_t descs,
+    uint64_t num_sels);
 
 /*
  * Add the descriptors to the LDT, starting with
  * the descriptor for 'first_selector'.
  */
 
-int
-i386_set_ldt(
-       uint32_t                *retval,
-       uint32_t                start_sel,
-       uint32_t                descs,  /* out */
-       uint32_t                num_sels)
+static int
+i386_set_ldt_impl(
+       uint32_t                *retval,
+       uint64_t                start_sel,
+       uint64_t                descs,  /* out */
+       uint64_t                num_sels)
 {
-       user_ldt_t      new_ldt, old_ldt;
+       user_ldt_t      new_ldt, old_ldt;
        struct real_descriptor *dp;
-       unsigned int    i;
-       unsigned int    min_selector = LDTSZ_MIN;       /* do not allow the system selectors to be changed */
-       task_t          task = current_task();
-       unsigned int    ldt_count;
+       unsigned int    i;
+       unsigned int    min_selector = LDTSZ_MIN;       /* do not allow the system selectors to be changed */
+       task_t          task = current_task();
+       unsigned int    ldt_count;
        kern_return_t err;
 
        if (start_sel != LDT_AUTO_ALLOC
            && (start_sel != 0 || num_sels != 0)
-           && (start_sel < min_selector || start_sel >= LDTSZ))
-           return EINVAL;
-       if (start_sel != LDT_AUTO_ALLOC
-           && (uint64_t)start_sel + (uint64_t)num_sels > LDTSZ) /* cast to uint64_t to detect wrap-around */
-           return EINVAL;
+           && (start_sel < min_selector || start_sel >= LDTSZ || num_sels > LDTSZ)) {
+               return EINVAL;
+       }
+       if (start_sel != LDT_AUTO_ALLOC && start_sel + num_sels > LDTSZ) {
+               return EINVAL;
+       }
 
        task_lock(task);
-       
+
        old_ldt = task->i386_ldt;
 
        if (start_sel == LDT_AUTO_ALLOC) {
-           if (old_ldt) {
-               unsigned int null_count;
-               struct real_descriptor null_ldt;
-               
-               bzero(&null_ldt, sizeof(null_ldt));
+               if (old_ldt) {
+                       unsigned int null_count;
+                       struct real_descriptor null_ldt;
 
-               /*
-                * Look for null selectors among the already-allocated
-                * entries.
-                */
-               null_count = 0;
-               i = 0;
-               while (i < old_ldt->count)
-               {
-                   if (!memcmp(&old_ldt->ldt[i++], &null_ldt, sizeof(null_ldt))) {
-                       null_count++;
-                       if (null_count == num_sels)
-                           break;  /* break out of while loop */
-                   } else {
+                       bzero(&null_ldt, sizeof(null_ldt));
+
+                       /*
+                        * Look for null selectors among the already-allocated
+                        * entries.
+                        */
                        null_count = 0;
-                   }
+                       i = 0;
+                       while (i < old_ldt->count) {
+                               if (!memcmp(&old_ldt->ldt[i++], &null_ldt, sizeof(null_ldt))) {
+                                       null_count++;
+                                       if (null_count == num_sels) {
+                                               break; /* break out of while loop */
+                                       }
+                               } else {
+                                       null_count = 0;
+                               }
+                       }
+
+                       /*
+                        * If we broke out of the while loop, i points to the selector
+                        * after num_sels null selectors.  Otherwise it points to the end
+                        * of the old LDTs, and null_count is the number of null selectors
+                        * at the end.
+                        *
+                        * Either way, there are null_count null selectors just prior to
+                        * the i-indexed selector, and either null_count >= num_sels,
+                        * or we're at the end, so we can extend.
+                        */
+                       start_sel = old_ldt->start + i - null_count;
+               } else {
+                       start_sel = LDTSZ_MIN;
                }
 
-               /*
-                * If we broke out of the while loop, i points to the selector
-                * after num_sels null selectors.  Otherwise it points to the end
-                * of the old LDTs, and null_count is the number of null selectors
-                * at the end. 
-                *
-                * Either way, there are null_count null selectors just prior to
-                * the i-indexed selector, and either null_count >= num_sels,
-                * or we're at the end, so we can extend.
-                */
-               start_sel = old_ldt->start + i - null_count;
-           } else {
-               start_sel = LDTSZ_MIN;
-           }
-               
-           if ((uint64_t)start_sel + (uint64_t)num_sels > LDTSZ) {
-               task_unlock(task);
-               return ENOMEM;
-           }
+               if (start_sel + num_sels > LDTSZ) {
+                       task_unlock(task);
+                       return ENOMEM;
+               }
        }
 
        if (start_sel == 0 && num_sels == 0) {
-           new_ldt = NULL;
+               new_ldt = NULL;
        } else {
-           /*
-            * Allocate new LDT
-            */
-
-           unsigned int    begin_sel = start_sel;
-           unsigned int    end_sel = begin_sel + num_sels;
-           
-           if (old_ldt != NULL) {
-               if (old_ldt->start < begin_sel)
-                   begin_sel = old_ldt->start;
-               if (old_ldt->start + old_ldt->count > end_sel)
-                   end_sel = old_ldt->start + old_ldt->count;
-           }
-
-           ldt_count = end_sel - begin_sel;
-           /* XXX allocation under task lock */
-           new_ldt = (user_ldt_t)kalloc(sizeof(struct user_ldt) + (ldt_count * sizeof(struct real_descriptor)));
-           if (new_ldt == NULL) {
-               task_unlock(task);
-               return ENOMEM;
-           }
-
-           new_ldt->start = begin_sel;
-           new_ldt->count = ldt_count;
-
-           /*
-            * Have new LDT.  If there was a an old ldt, copy descriptors
-            * from old to new.
-            */
-           if (old_ldt) {
-               bcopy(&old_ldt->ldt[0],
-                     &new_ldt->ldt[old_ldt->start - begin_sel],
-                     old_ldt->count * sizeof(struct real_descriptor));
-
                /*
-                * If the old and new LDTs are non-overlapping, fill the 
-                * center in with null selectors.
+                * Allocate new LDT
                 */
-                                
-               if (old_ldt->start + old_ldt->count < start_sel)
-                   bzero(&new_ldt->ldt[old_ldt->count],
-                         (start_sel - (old_ldt->start + old_ldt->count)) * sizeof(struct real_descriptor));
-               else if (old_ldt->start > start_sel + num_sels)
-                   bzero(&new_ldt->ldt[num_sels],
-                         (old_ldt->start - (start_sel + num_sels)) * sizeof(struct real_descriptor));
-           }
-
-           /*
-            * Install new descriptors.
-            */
-           if (descs != 0) {
-                   /* XXX copyin under task lock */
-               err = copyin(descs, (char *)&new_ldt->ldt[start_sel - begin_sel],
-                            num_sels * sizeof(struct real_descriptor));
-               if (err != 0)
-               {
-                   task_unlock(task);
-                   user_ldt_free(new_ldt);
-                   return err;
+
+               unsigned int    begin_sel = (unsigned int)start_sel;
+               unsigned int    end_sel = (unsigned int)begin_sel +
+                   (unsigned int)num_sels;
+
+               if (old_ldt != NULL) {
+                       if (old_ldt->start < begin_sel) {
+                               begin_sel = old_ldt->start;
+                       }
+                       if (old_ldt->start + old_ldt->count > end_sel) {
+                               end_sel = old_ldt->start + old_ldt->count;
+                       }
                }
-           } else {
-               bzero(&new_ldt->ldt[start_sel - begin_sel], num_sels * sizeof(struct real_descriptor));
-           }
-           /*
-            * Validate descriptors.
-            * Only allow descriptors with user privileges.
-            */
-           for (i = 0, dp = (struct real_descriptor *) &new_ldt->ldt[start_sel - begin_sel];
-                i < num_sels;
-                i++, dp++)
-           {
-               switch (dp->access & ~ACC_A) {
-                   case 0:
-                   case ACC_P:
-                       /* valid empty descriptor, clear Present preemptively */
-                       dp->access &= (~ACC_P & 0xff);
-                       break;
-                   case ACC_P | ACC_PL_U | ACC_DATA:
-                   case ACC_P | ACC_PL_U | ACC_DATA_W:
-                   case ACC_P | ACC_PL_U | ACC_DATA_E:
-                   case ACC_P | ACC_PL_U | ACC_DATA_EW:
-                   case ACC_P | ACC_PL_U | ACC_CODE:
-                   case ACC_P | ACC_PL_U | ACC_CODE_R:
-                   case ACC_P | ACC_PL_U | ACC_CODE_C:
-                   case ACC_P | ACC_PL_U | ACC_CODE_CR:
-                       break;
-                   default:
+
+               ldt_count = end_sel - begin_sel;
+               /* XXX allocation under task lock */
+               new_ldt = (user_ldt_t)kalloc(sizeof(struct user_ldt) + (ldt_count * sizeof(struct real_descriptor)));
+               if (new_ldt == NULL) {
                        task_unlock(task);
-                       user_ldt_free(new_ldt);
-                       return EACCES;
+                       return ENOMEM;
                }
-               /* Reject attempts to create segments with 64-bit granules */
-               if (dp->granularity & SZ_64) {
-                       task_unlock(task);
-                       user_ldt_free(new_ldt);
-                       return EACCES;
+
+               new_ldt->start = begin_sel;
+               new_ldt->count = ldt_count;
+
+               /*
+                * Have new LDT.  If there was a an old ldt, copy descriptors
+                * from old to new.
+                */
+               if (old_ldt) {
+                       bcopy(&old_ldt->ldt[0],
+                           &new_ldt->ldt[old_ldt->start - begin_sel],
+                           old_ldt->count * sizeof(struct real_descriptor));
+
+                       /*
+                        * If the old and new LDTs are non-overlapping, fill the
+                        * center in with null selectors.
+                        */
+
+                       if (old_ldt->start + old_ldt->count < start_sel) {
+                               bzero(&new_ldt->ldt[old_ldt->count],
+                                   (start_sel - (old_ldt->start + old_ldt->count)) * sizeof(struct real_descriptor));
+                       } else if (old_ldt->start > start_sel + num_sels) {
+                               bzero(&new_ldt->ldt[num_sels],
+                                   (old_ldt->start - (start_sel + num_sels)) * sizeof(struct real_descriptor));
+                       }
+               }
+
+               /*
+                * Install new descriptors.
+                */
+               if (descs != 0) {
+                       /* XXX copyin under task lock */
+                       err = copyin(descs, (char *)&new_ldt->ldt[start_sel - begin_sel],
+                           num_sels * sizeof(struct real_descriptor));
+                       if (err != 0) {
+                               task_unlock(task);
+                               user_ldt_free(new_ldt);
+                               return err;
+                       }
+               } else {
+                       bzero(&new_ldt->ldt[start_sel - begin_sel], num_sels * sizeof(struct real_descriptor));
+               }
+               /*
+                * Validate descriptors.
+                * Only allow descriptors with user privileges.
+                */
+               for (i = 0, dp = (struct real_descriptor *) &new_ldt->ldt[start_sel - begin_sel];
+                   i < num_sels;
+                   i++, dp++) {
+                       switch (dp->access & ~ACC_A) {
+                       case 0:
+                       case ACC_P:
+                               /* valid empty descriptor, clear Present preemptively */
+                               dp->access &= (~ACC_P & 0xff);
+                               break;
+                       case ACC_P | ACC_PL_U | ACC_DATA:
+                       case ACC_P | ACC_PL_U | ACC_DATA_W:
+                       case ACC_P | ACC_PL_U | ACC_DATA_E:
+                       case ACC_P | ACC_PL_U | ACC_DATA_EW:
+                       case ACC_P | ACC_PL_U | ACC_CODE:
+                       case ACC_P | ACC_PL_U | ACC_CODE_R:
+                       case ACC_P | ACC_PL_U | ACC_CODE_C:
+                       case ACC_P | ACC_PL_U | ACC_CODE_CR:
+                               break;
+                       default:
+                               task_unlock(task);
+                               user_ldt_free(new_ldt);
+                               return EACCES;
+                       }
+                       /* Reject attempts to create segments with 64-bit granules */
+                       /* Note this restriction is still correct, even when
+                        * executing as a 64-bit process (we want to maintain a single
+                        * 64-bit selector (located in the GDT)).
+                        */
+                       if (dp->granularity & SZ_64) {
+                               task_unlock(task);
+                               user_ldt_free(new_ldt);
+                               return EACCES;
+                       }
                }
-           }
        }
 
        task->i386_ldt = new_ldt; /* new LDT for task */
@@ -277,32 +288,36 @@ i386_set_ldt(
         * rendezvoused with all CPUs, in case another thread
         * in this task was in the process of context switching.
         */
-       if (old_ldt)
-           user_ldt_free(old_ldt);
+       if (old_ldt) {
+               user_ldt_free(old_ldt);
+       }
 
-       *retval = start_sel;
+       *retval = (uint32_t)start_sel;
 
        return 0;
 }
 
-int
-i386_get_ldt(
-       uint32_t                *retval,
-       uint32_t                start_sel,
-       uint32_t                descs,  /* out */
-       uint32_t                num_sels)
+static int
+i386_get_ldt_impl(
+       uint32_t                *retval,
+       uint64_t                start_sel,
+       uint64_t                descs,  /* out */
+       uint64_t                num_sels)
 {
-       user_ldt_t      user_ldt;
-       task_t          task = current_task();
-       unsigned int    ldt_count;
-       kern_return_t   err;
-
-       if (start_sel >= LDTSZ)
-           return EINVAL;
-       if ((uint64_t)start_sel + (uint64_t)num_sels > LDTSZ)
-           return EINVAL;
-       if (descs == 0)
-           return EINVAL;
+       user_ldt_t      user_ldt;
+       task_t          task = current_task();
+       unsigned int    ldt_count;
+       kern_return_t   err;
+
+       if (start_sel >= LDTSZ || num_sels > LDTSZ) {
+               return EINVAL;
+       }
+       if (start_sel + num_sels > LDTSZ) {
+               return EINVAL;
+       }
+       if (descs == 0) {
+               return EINVAL;
+       }
 
        task_lock(task);
 
@@ -313,21 +328,22 @@ i386_get_ldt(
         * copy out the descriptors
         */
 
-       if (user_ldt != 0)
-           ldt_count = user_ldt->start + user_ldt->count;
-       else
-           ldt_count = LDTSZ_MIN;
+       if (user_ldt != 0) {
+               ldt_count = user_ldt->start + user_ldt->count;
+       } else {
+               ldt_count = LDTSZ_MIN;
+       }
 
 
-       if (start_sel < ldt_count)
-       {
-           unsigned int copy_sels = num_sels;
+       if (start_sel < ldt_count) {
+               unsigned int copy_sels = (unsigned int)num_sels;
 
-           if (start_sel + num_sels > ldt_count)
-               copy_sels = ldt_count - start_sel;
+               if (start_sel + num_sels > ldt_count) {
+                       copy_sels = ldt_count - (unsigned int)start_sel;
+               }
 
-           err = copyout((char *)(current_ldt() + start_sel),
-                         descs, copy_sels * sizeof(struct real_descriptor));
+               err = copyout((char *)(current_ldt() + start_sel),
+                   descs, copy_sels * sizeof(struct real_descriptor));
        }
 
        task_unlock(task);
@@ -339,23 +355,24 @@ i386_get_ldt(
 
 void
 user_ldt_free(
-       user_ldt_t      user_ldt)
+       user_ldt_t      user_ldt)
 {
        kfree(user_ldt, sizeof(struct user_ldt) + (user_ldt->count * sizeof(struct real_descriptor)));
 }
 
 user_ldt_t
 user_ldt_copy(
-       user_ldt_t      user_ldt)
+       user_ldt_t      user_ldt)
 {
        if (user_ldt != NULL) {
-           size_t      size = sizeof(struct user_ldt) + (user_ldt->count * sizeof(struct real_descriptor));
-           user_ldt_t  new_ldt = (user_ldt_t)kalloc(size);
-           if (new_ldt != NULL)
-               bcopy(user_ldt, new_ldt, size);
-           return new_ldt;
+               size_t      size = sizeof(struct user_ldt) + (user_ldt->count * sizeof(struct real_descriptor));
+               user_ldt_t  new_ldt = (user_ldt_t)kalloc(size);
+               if (new_ldt != NULL) {
+                       bcopy(user_ldt, new_ldt, size);
+               }
+               return new_ldt;
        }
-       
+
        return 0;
 }
 
@@ -363,10 +380,10 @@ void
 user_ldt_set_action(
        void *arg)
 {
-       task_t          arg_task = (task_t)arg;
+       task_t          arg_task = (task_t)arg;
 
-       if (arg_task == current_task()) {
-           user_ldt_set(current_thread());
+       if (arg_task == current_task()) {
+               user_ldt_set(current_thread());
        }
 }
 
@@ -378,26 +395,72 @@ void
 user_ldt_set(
        thread_t thread)
 {
-        task_t         task = thread->task;
-       user_ldt_t      user_ldt;
+       task_t          task = thread->task;
+       user_ldt_t      user_ldt;
 
        user_ldt = task->i386_ldt;
 
        if (user_ldt != 0) {
-           struct real_descriptor *ldtp = (struct real_descriptor *)current_ldt();
+               struct real_descriptor *ldtp = current_ldt();
+
+               if (user_ldt->start > LDTSZ_MIN) {
+                       bzero(&ldtp[LDTSZ_MIN],
+                           sizeof(struct real_descriptor) * (user_ldt->start - LDTSZ_MIN));
+               }
 
-           if (user_ldt->start > LDTSZ_MIN) {
-               bzero(&ldtp[LDTSZ_MIN],
-                     sizeof(struct real_descriptor) * (user_ldt->start - LDTSZ_MIN));
-           }
-           
-           bcopy(user_ldt->ldt, &ldtp[user_ldt->start],
-                 sizeof(struct real_descriptor) * (user_ldt->count));
+               bcopy(user_ldt->ldt, &ldtp[user_ldt->start],
+                   sizeof(struct real_descriptor) * (user_ldt->count));
 
-           gdt_desc_p(USER_LDT)->limit_low = (uint16_t)((sizeof(struct real_descriptor) * (user_ldt->start + user_ldt->count)) - 1);
+               gdt_desc_p(USER_LDT)->limit_low = (uint16_t)((sizeof(struct real_descriptor) * (user_ldt->start + user_ldt->count)) - 1);
 
-           ml_cpu_set_ldt(USER_LDT);
+               ml_cpu_set_ldt(USER_LDT);
        } else {
-           ml_cpu_set_ldt(KERNEL_LDT);
+               ml_cpu_set_ldt(KERNEL_LDT);
        }
 }
+
+/* For 32-bit processes, called via machdep_syscall() */
+int
+i386_set_ldt(
+       uint32_t                *retval,
+       uint32_t                start_sel,
+       uint32_t                descs,  /* out */
+       uint32_t                num_sels)
+{
+       return i386_set_ldt_impl(retval, (uint64_t)start_sel, (uint64_t)descs,
+                  (uint64_t)num_sels);
+}
+
+/* For 64-bit processes, called via machdep_syscall64() */
+int
+i386_set_ldt64(
+       uint32_t                *retval,
+       uint64_t                start_sel,
+       uint64_t                descs,  /* out */
+       uint64_t                num_sels)
+{
+       return i386_set_ldt_impl(retval, start_sel, descs, num_sels);
+}
+
+/* For 32-bit processes, called via machdep_syscall() */
+int
+i386_get_ldt(
+       uint32_t                *retval,
+       uint32_t                start_sel,
+       uint32_t                descs,  /* out */
+       uint32_t                num_sels)
+{
+       return i386_get_ldt_impl(retval, (uint64_t)start_sel, (uint64_t)descs,
+                  (uint64_t)num_sels);
+}
+
+/* For 64-bit processes, called via machdep_syscall64() */
+int
+i386_get_ldt64(
+       uint32_t                *retval,
+       uint64_t                start_sel,
+       uint64_t                descs,  /* out */
+       uint64_t                num_sels)
+{
+       return i386_get_ldt_impl(retval, start_sel, descs, num_sels);
+}