If a port is inlined in a message, the user has to use mach_port_name_inlined_t to define each port. Out of line memory continues to use mach_port_name_t since that memory has to be copied to the kernel anyway.
Both copyinmsg and copyoutmsg can be reduced to nothing (if we ignore USER32) as a follow up but kept this patch simple for ease of review. --- i386/i386/copy_user.h | 12 ++++---- include/mach/message.h | 18 ++++++++++++ x86_64/copy_user.c | 67 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 14 deletions(-) diff --git a/i386/i386/copy_user.h b/i386/i386/copy_user.h index 5cdbfa80..3d1c7278 100644 --- a/i386/i386/copy_user.h +++ b/i386/i386/copy_user.h @@ -87,16 +87,14 @@ static inline int copyout_port(const mach_port_t *kaddr, mach_port_name_t *uaddr #endif /* __x86_64__ */ } -// XXX we could add another field to kmsg to store the user-side size, but then we -// should check if we can obtain it for rpc and notifications originating from -// the kernel -#ifndef __x86_64__ +#if defined(__x86_64__) && defined(USER32) +/* For 32 bit userland, kernel and user land messages are not the same size. */ +size_t msg_usize(const mach_msg_header_t *kmsg); +#else static inline size_t msg_usize(const mach_msg_header_t *kmsg) { return kmsg->msgh_size; } -#else /* __x86_64__ */ -size_t msg_usize(const mach_msg_header_t *kmsg); -#endif /* __x86_64__ */ +#endif /* __x86_64__ && USER32 */ #endif /* COPY_USER_H */ diff --git a/include/mach/message.h b/include/mach/message.h index 17d3592f..21e99896 100644 --- a/include/mach/message.h +++ b/include/mach/message.h @@ -221,6 +221,24 @@ typedef unsigned int mach_msg_type_name_t; typedef unsigned int mach_msg_type_size_t; typedef natural_t mach_msg_type_number_t; +/** + * Structure used for inlined port rights in messages. + * + * We use this to avoid having to perform message resizing in the kernel + * since userspace port rights might be smaller than kernel ports in 64 bit + * architectures. + */ +typedef struct { + union { + mach_port_name_t name; +#ifdef KERNEL + mach_port_t kernel_port; +#else + uintptr_t kernel_port_do_not_use; +#endif /* KERNEL */ + }; +} mach_port_name_inlined_t; + typedef struct { #ifdef __x86_64__ /* diff --git a/x86_64/copy_user.c b/x86_64/copy_user.c index 0d3f301b..c6e125d9 100644 --- a/x86_64/copy_user.c +++ b/x86_64/copy_user.c @@ -265,8 +265,15 @@ static inline int copyout_unpack_msg_type(vm_offset_t kaddr, mach_msg_type_size_t orig_size = kmtl->msgtl_size; int ret; - if (MACH_MSG_TYPE_PORT_ANY(kmtl->msgtl_name)) + if (MACH_MSG_TYPE_PORT_ANY(kmtl->msgtl_name)) { +#ifdef USER32 kmtl->msgtl_size = bytes_to_descsize(sizeof(mach_port_name_t)); +#else + /* 64 bit ABI uses mach_port_name_inlined_t for inlined ports. */ + if (!kmt->msgt_inline) + kmtl->msgtl_size = bytes_to_descsize(sizeof(mach_port_name_t)); +#endif + } ret = copyout_mach_msg_type_long(kmtl, (void*)uaddr); kmtl->msgtl_size = orig_size; if (ret) @@ -283,8 +290,15 @@ static inline int copyout_unpack_msg_type(vm_offset_t kaddr, mach_msg_type_size_t orig_size = kmt->msgt_size; int ret; - if (MACH_MSG_TYPE_PORT_ANY(kmt->msgt_name)) + if (MACH_MSG_TYPE_PORT_ANY(kmt->msgt_name)) { +#ifdef USER32 kmt->msgt_size = bytes_to_descsize(sizeof(mach_port_name_t)); +#else + /* 64 bit ABI uses mach_port_name_inlined_t for inlined ports. */ + if (!kmt->msgt_inline) + kmt->msgt_size = bytes_to_descsize(sizeof(mach_port_name_t)); +#endif + } ret = copyout_mach_msg_type(kmt, (void *)uaddr); kmt->msgt_size = orig_size; if (ret) @@ -299,8 +313,10 @@ static inline int copyout_unpack_msg_type(vm_offset_t kaddr, return 0; } +#ifdef USER32 /* - * Compute the user-space size of a message still in the kernel. + * Compute the user-space size of a message still in the kernel when processing + * messages from 32bit userland. * The message may be originating from userspace (in which case we could * optimize this by keeping the usize around) or from kernel space (we could * optimize if the message structure is fixed and known in advance). @@ -333,7 +349,8 @@ size_t msg_usize(const mach_msg_header_t *kmsg) { if (MACH_MSG_TYPE_PORT_ANY(name)) { - saddr += sizeof(mach_port_t) * number; + const vm_size_t length = sizeof(mach_port_t) * number; + saddr += length; usize += sizeof(mach_port_name_t) * number; } else @@ -355,6 +372,7 @@ size_t msg_usize(const mach_msg_header_t *kmsg) } return usize; } +#endif /* USER32 */ /* * Expand the msg header and, if required, the msg body (ports, pointers) @@ -423,6 +441,9 @@ int copyinmsg (const void *userbuf, void *kernelbuf, const size_t usize, const s { if (MACH_MSG_TYPE_PORT_ANY(name)) { +#ifdef USER32 + if (size != bytes_to_descsize(sizeof(mach_port_name_t))) + return 1; if ((usaddr + sizeof(mach_port_name_t)*number) > ueaddr) return 1; adjust_msg_type_size(ktaddr, sizeof(mach_port_t) - sizeof(mach_port_name_t)); @@ -433,6 +454,17 @@ int copyinmsg (const void *userbuf, void *kernelbuf, const size_t usize, const s ksaddr += sizeof(mach_port_t); usaddr += sizeof(mach_port_name_t); } +#else + if (size != bytes_to_descsize(sizeof(mach_port_name_inlined_t))) + return 1; + const vm_size_t length = number * sizeof(mach_port_name_inlined_t); + if ((usaddr + length) > ueaddr) + return 1; + if (copyin((void*)usaddr, (void*)ksaddr, length)) + return 1; + usaddr += length; + ksaddr += length; +#endif } else { @@ -451,9 +483,13 @@ int copyinmsg (const void *userbuf, void *kernelbuf, const size_t usize, const s if ((usaddr + sizeof(rpc_vm_offset_t)) > ueaddr) return 1; - // out-of-line port arrays are expanded in ipc_kmsg_copyin_body() - if (MACH_MSG_TYPE_PORT_ANY(name)) + /* out-of-line port arrays are always arrays of mach_port_name_t (4 bytes) + * and are expanded in ipc_kmsg_copyin_body() */ + if (MACH_MSG_TYPE_PORT_ANY(name)) { + if (size != bytes_to_descsize(sizeof(mach_port_name_t))) + return 1; adjust_msg_type_size(ktaddr, sizeof(mach_port_t) - sizeof(mach_port_name_t)); + } if (copyin_address((rpc_vm_offset_t*)usaddr, (vm_offset_t*)ksaddr)) return 1; @@ -470,6 +506,10 @@ int copyinmsg (const void *userbuf, void *kernelbuf, const size_t usize, const s kmsg->msgh_size = sizeof(mach_msg_header_t) + ksaddr - (vm_offset_t)(kmsg + 1); assert(kmsg->msgh_size <= ksize); +#ifndef USER32 + if (kmsg->msgh_size != usize) + return 1; +#endif return 0; } @@ -519,6 +559,7 @@ int copyoutmsg (const void *kernelbuf, void *userbuf, const size_t ksize) { if (MACH_MSG_TYPE_PORT_ANY(name)) { +#ifdef USER32 for (int i=0; i<number; i++) { if (copyout_port((mach_port_t*)ksaddr, (mach_port_name_t*)usaddr)) @@ -526,10 +567,18 @@ int copyoutmsg (const void *kernelbuf, void *userbuf, const size_t ksize) ksaddr += sizeof(mach_port_t); usaddr += sizeof(mach_port_name_t); } +#else + if (size != bytes_to_descsize(sizeof(mach_port_name_inlined_t))) + return 1; + const vm_size_t length = number * sizeof(mach_port_name_inlined_t); + if (copyout((void*)ksaddr, (void*)usaddr, length)) + return 1; + ksaddr += length; + usaddr += length; +#endif } else { - // type that doesn't need change size_t n = descsize_to_bytes(size); if (copyout((void*)ksaddr, (void*)usaddr, n*number)) return 1; @@ -554,6 +603,10 @@ int copyoutmsg (const void *kernelbuf, void *userbuf, const size_t ksize) usize = sizeof(mach_msg_user_header_t) + usaddr - (vm_offset_t)(umsg + 1); if (copyout(&usize, &umsg->msgh_size, sizeof(umsg->msgh_size))) return 1; +#ifndef USER32 + if (usize != ksize) + return 1; +#endif return 0; -- 2.39.2