#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern struct sysentvec ia32_freebsd_sysvec; /* * These use 64 bit kernel types that correspond to what * the userland part does. */ struct mmap64_args { void *addr; /* 0 */ size_t len; /* 8 */ off_t pos; /* 16 */ int prot; /* 24 */ int flags; /* 28 */ int fd; /* 32 */ }; struct msync64_args { void *addr; /* 0 */ size_t len; /* 8 */ int flags; /* 16 */ }; struct munmap64_args { void *addr; /* 0 */ size_t len; /* 8 */ }; /* * 64 bit syscall service function. */ struct emm64_syscall_args { int cmd; void *uaddr; }; /* Values for cmd */ #define EMM64_GETSEGS 0 #define EMM64_MMAP 1 #define EMM64_MUNMAP 2 #define EMM64_MSYNC 3 static int emm64_num = NO_SYSCALL; SYSCTL_INT(_machdep, OID_AUTO, emm64_num, CTLFLAG_RD, &emm64_num, 0, "syscall number for emm64()"); static int freebsd32_mmap_wrap(struct thread *td, struct freebsd32_mmap_args *uap); static int freebsd32_munmap_wrap(struct thread *td, struct munmap_args *uap); static int freebsd32_shmsys_wrap(struct thread *td, struct freebsd32_shmsys_args *uap); static int freebsd32_shmat_wrap(struct thread *td, struct shmat_args *uap); static int emm64_syscall(struct thread *td, struct emm64_syscall_args *uap) { uint64_t ret; int error; u_int r; union { struct mmap64_args mm; struct msync64_args ms; struct munmap64_args mu; } u; union { struct mmap_args mm; struct msync_args ms; struct munmap_args mu; } a; switch (uap->cmd) { case EMM64_GETSEGS: r = GSEL(GUCODE_SEL, SEL_UPL); /* 43 */ r |= GSEL(GUCODE32_SEL, SEL_UPL) << 16; /* 27 */ td->td_retval[0] = r; error = 0; break; case EMM64_MMAP: error = copyin(uap->uaddr, &u.mm, sizeof(u.mm)); if (error) break; if (u.mm.addr == 0 && (u.mm.flags & MAP_FIXED) == 0) a.mm.addr = (void *)0x100000000ul; else a.mm.addr = u.mm.addr; a.mm.len = u.mm.len; a.mm.prot = u.mm.prot; a.mm.flags = u.mm.flags; a.mm.fd = u.mm.fd; a.mm.pad = 0; a.mm.pos = u.mm.pos; error = mmap(td, &a.mm); /* Expand the quad return into two parts for eax and edx */ ret = td->td_retval[0]; td->td_retval[0] = ret & 0xffffffff; /* %eax */ td->td_retval[1] = ret >> 32; /* %edx */ break; case EMM64_MUNMAP: error = copyin(uap->uaddr, &u.mu, sizeof(u.mu)); if (error) break; a.mu.addr = u.mu.addr; a.mu.len = u.mu.len; #if 0 printf("munmap64: %p, len 0x%lx\n", a.mu.addr, a.mu.len); */ #endif error = munmap(td, &a.mu); /* int return, no need to split */ break; case EMM64_MSYNC: error = copyin(uap->uaddr, &u.ms, sizeof(u.ms)); if (error) break; a.ms.addr = u.ms.addr; a.ms.len = u.ms.len; a.ms.flags = u.ms.flags; #if 0 printf("msync64: %p, len 0x%lx, flags 0x%x\n", a.ms.addr, a.ms.len, a.ms.flags); #endif error = msync(td, &a.ms); /* int return, no need to split */ break; default: error = EOPNOTSUPP; break; } #if 0 printf("emm64: %d 0x%lx -> 0x%lx 0x%lx\n", uap->cmd, ret, td->td_retval[0], td->td_retval[1]); #endif return (error); } extern struct sysent freebsd32_sysent[]; /* Special magic for installing a 32-bit only syscall */ static int syscall32_register(int *offset, struct sysent *new_sysent, struct sysent *old_sysent) { int i; if (*offset == NO_SYSCALL) { for (i = 210; i < 219; ++i) { if (freebsd32_sysent[i].sy_call == (sy_call_t *)nosys) break; } if (i == 219) return ENFILE; *offset = i; } *old_sysent = freebsd32_sysent[*offset]; freebsd32_sysent[*offset] = *new_sysent; return 0; } static int syscall32_deregister(int *offset, struct sysent *old_sysent) { if (*offset && *offset != NO_SYSCALL) freebsd32_sysent[*offset] = *old_sysent; *offset = NO_SYSCALL; return 0; } /* Helper functions */ static void * findsym(const char *name) { linker_file_t lf; caddr_t rv; /* * Cheat, and take advantage of knowledge that linker_kernel_file * is always the first item in the linker_files tailq. Use * linker_file_foreach() on freebsd-7. */ for (lf = linker_kernel_file; lf; lf = TAILQ_NEXT(lf, link)) { rv = linker_file_lookup_symbol(lf, name, 0); if (rv) { return ((void *)rv); } } return (NULL); } static int (*kern_shmctl_p)(struct thread *td, int shmid, int cmd, void *buf, size_t *bufsz); static vm_offset_t old_sv_maxuser; static struct sysent old_mmap32; static struct sysent new_mmap32 = { SYF_MPSAFE | (sizeof(struct freebsd32_mmap_args) / sizeof(register_t)), (sy_call_t *)freebsd32_mmap_wrap, }; static struct sysent old_munmap32; static struct sysent new_munmap32 = { SYF_MPSAFE | (sizeof(struct munmap_args) / sizeof(register_t)), (sy_call_t *)freebsd32_munmap_wrap, }; static struct sysent old_shmsys32; static struct sysent new_shmsys32 = { SYF_MPSAFE | (sizeof(struct freebsd32_shmsys_args) / sizeof(register_t)), (sy_call_t *)freebsd32_shmsys_wrap, }; static struct sysent old_shmat32; static struct sysent new_shmat32 = { SYF_MPSAFE | (sizeof(struct shmat_args) / sizeof(register_t)), (sy_call_t *)freebsd32_shmat_wrap, }; static int emm64_module_handler(struct module *mod, int what, void *arg) { struct syscall_module_data *data = (struct syscall_module_data*)arg; modspecific_t ms; int error; int i; switch (what) { case MOD_LOAD: error = syscall32_register(data->offset, data->new_sysent, &data->old_sysent); if (error) { data->offset = NULL; return error; } ms.intval = *data->offset; MOD_XLOCK; module_setspecific(mod, &ms); MOD_XUNLOCK; old_sv_maxuser = ia32_freebsd_sysvec.sv_maxuser; ia32_freebsd_sysvec.sv_maxuser = VM_MAXUSER_ADDRESS; i = SYS_mmap; /* SYS_freebsd6_mmap on 7.x+ */ error = syscall32_register(&i, &new_mmap32, &old_mmap32); i = SYS_munmap; /* SYS_freebsd6_munmap on 7.x+ */ error = syscall32_register(&i, &new_munmap32, &old_munmap32); i = SYS_shmsys; error = syscall32_register(&i, &new_shmsys32, &old_shmsys32); i = SYS_shmat; error = syscall32_register(&i, &new_shmat32, &old_shmat32); return error; case MOD_UNLOAD: if (data->offset == NULL) /* MOD_LOAD failed */ return (0); i = SYS_mmap; /* SYS_freebsd6_mmap on 7.x+ */ error = syscall32_deregister(&i, &old_mmap32); i = SYS_munmap; /* SYS_freebsd6_munmap on 7.x+ */ error = syscall32_deregister(&i, &old_munmap32); i = SYS_shmsys; error = syscall32_deregister(&i, &old_shmsys32); i = SYS_shmat; error = syscall32_deregister(&i, &old_shmat32); ia32_freebsd_sysvec.sv_maxuser = old_sv_maxuser; error = syscall32_deregister(data->offset, &data->old_sysent); return error; default: return EOPNOTSUPP; } return 0; } static struct sysent emm64_sysent = { sizeof(struct emm64_syscall_args) / sizeof(register_t), (sy_call_t *)emm64_syscall, }; static struct syscall_module_data emm64_syscall_mod = { NULL, NULL, &emm64_num, &emm64_sysent, { 0, NULL } }; static moduledata_t emm64_mod = { "emm64", emm64_module_handler, &emm64_syscall_mod }; DECLARE_MODULE(emm64, emm64_mod, SI_SUB_INTRINSIC_POST, SI_ORDER_ANY); static int freebsd32_mmap_wrap(struct thread *td, struct freebsd32_mmap_args *uap) { int error; vm_offset_t addr; /* Check for silly requests */ if (uap->flags & MAP_FIXED) { if ((vm_offset_t)uap->addr + uap->len > 0xfffff000) return (EINVAL); } else if (uap->addr != 0) { if ((vm_offset_t)uap->addr + uap->len > 0xfffff000) return (EINVAL); } error = old_mmap32.sy_call(td, uap); if (error == 0) { addr = td->td_retval[0]; if (addr + uap->len > 0xfffff000) { struct munmap_args ap; ap.addr = (void *)addr; ap.len = uap->len; munmap(td, &ap); return (ENOMEM); } } return (error); } static int freebsd32_munmap_wrap(struct thread *td, struct munmap_args *uap) { int error; /* Check for silly requests */ if ((vm_offset_t)uap->addr + uap->len > 0xfffff000) return (EINVAL); error = old_munmap32.sy_call(td, uap); return (error); } /* * Intercept the shmat function (0) of the shmsys syscall. If the * segment would be forced outside of 32 bit space, attempt to detach * and fake an error. */ static int freebsd32_shmsys_wrap(struct thread *td, struct freebsd32_shmsys_args *uap) { int error; vm_offset_t addr; vm_offset_t len; struct shmid_ds shds; size_t shdslen; int rv; error = old_shmsys32.sy_call(td, uap); if (error == 0) { switch (uap->which) { case 0: { /* shmat */ addr = td->td_retval[0]; if (kern_shmctl_p == NULL) { kern_shmctl_p = findsym("kern_shmctl"); #if 0 printf("kern_shmctl_p = %p\n", kern_shmctl_p); #endif } if (kern_shmctl_p) { register_t oretval0; /* sigh, save/restore td_retval[0]; */ oretval0 = td->td_retval[0]; shdslen = sizeof(shds); rv = kern_shmctl_p(td, uap->a3, SHM_STAT, &shds, &shdslen); td->td_retval[0] = oretval0; if (rv == 0 && shdslen >= sizeof(shds)) { len = shds.shm_segsz; #if 0 printf("length = 0x%lx\n", len); #endif } else { len = 0; } } else { len = 0; /* XXX */ } if (addr + len > 0xfffff000) { /* Native shmdt, because shmdt32 can't express it */ struct shmdt_args ap; ap.shmaddr = (void *)addr; sysent[SYS_shmdt].sy_call(td, &ap); td->td_retval[0] = -1; return (ENOMEM); } } } } return (error); } /* Similar to above, but direct dispatch syscall */ static int freebsd32_shmat_wrap(struct thread *td, struct shmat_args *uap) { int error; vm_offset_t addr; vm_offset_t len; struct shmid_ds shds; size_t shdslen; int rv; error = old_shmat32.sy_call(td, uap); if (error == 0) { addr = td->td_retval[0]; if (kern_shmctl_p == NULL) { kern_shmctl_p = findsym("kern_shmctl"); #if 0 printf("kern_shmctl_p = %p\n", kern_shmctl_p); #endif } if (kern_shmctl_p) { register_t oretval0; /* sigh, save/restore td_retval[0]; */ oretval0 = td->td_retval[0]; shdslen = sizeof(shds); rv = kern_shmctl_p(td, uap->shmid, SHM_STAT, &shds, &shdslen); td->td_retval[0] = oretval0; if (rv == 0 && shdslen >= sizeof(shds)) { len = shds.shm_segsz; #if 0 printf("length = 0x%lx\n", len); #endif } else { len = 0; } } else { len = 0; /* XXX */ } if (addr + len > 0xfffff000) { /* Native shmdt, because shmdt32 can't express it */ struct shmdt_args ap; ap.shmaddr = (void *)addr; sysent[SYS_shmdt].sy_call(td, &ap); td->td_retval[0] = -1; return (ENOMEM); } } return (error); }