3

I've got dylib file for which I prevent access in kernel level. I use driver that listen to kauth vnode scope with callback that prevent any access to this file.

However, I've noticed that coreSymbolication framework can bypass this authorization flow after 4 retries to access the file, and access the file memory directly as it can be shown in the following backtrace :

frame #2: 0xffffff7f87ac4784 IOStorageFamily`dkreadwrite(dkr=0xffffff8062fca3e0, dkrtype=<unavailable>) at IOMediaBSDClient.cpp:2975 [opt]
frame #3: 0xffffff80072d8734 kernel`spec_strategy(ap=<unavailable>) at spec_vnops.c:2409 [opt]
frame #4: 0xffffff8007282212 kernel`buf_strategy(devvp=<unavailable>, ap=<unavailable>) at vfs_bio.c:1379 [opt]
frame #5: 0xffffff800728ad42 kernel`cluster_io [inlined] VNOP_STRATEGY(bp=0xffffff8062fca3e0) at kpi_vfs.c:5696 [opt]
frame #6: 0xffffff800728ad0b kernel`cluster_io(vp=0xffffff80145a8c18, upl=0xffffff80107f7700, upl_offset=0x0000000000004000, f_offset=40960, non_rounded_size=0, flags=141, real_bp=<unavailable>, iostate=<unavailable>, callback=<unavailable>, callback_arg=<unavailable>) at vfs_cluster.c:1801 [opt]
frame #7: 0xffffff800728b8f5 kernel`cluster_pagein_ext(vp=0xffffff80145a8c18, upl=0xffffff80107f7700, upl_offset=<unavailable>, f_offset=24576, size=<unavailable>, filesize=-549414335464, flags=<unavailable>, callback=<unavailable>, callback_arg=<unavailable>) at vfs_cluster.c:2171 [opt]
frame #8: 0xffffff800728b7e5 kernel`cluster_pagein(vp=<unavailable>, upl=<unavailable>, upl_offset=<unavailable>, f_offset=<unavailable>, size=<unavailable>, filesize=<unavailable>, flags=0) at vfs_cluster.c:2116 [opt]
frame #9: 0xffffff7f893d60be
frame #10: 0xffffff80075a811a kernel`vnode_pagein [inlined] VNOP_PAGEIN(size=24576, flags=341478424, ctx=<unavailable>) at kpi_vfs.c:5273 [opt]
frame #11: 0xffffff80075a80d3 kernel`vnode_pagein(vp=0xffffff80145a8c18, upl=<unavailable>, upl_offset=<unavailable>, f_offset=24576, size=<unavailable>, flags=341478424, errorp=<unavailable>) at vnode_pager.c:593 [opt]
frame #12: 0xffffff80070dc119 kernel`vnode_pager_cluster_read(vnode_object=0xffffff80145a48e8, base_offset=24576, offset=<unavailable>, io_streaming=<unavailable>, cnt=0x0000000000004000) at bsd_vm.c:851 [opt]
frame #13: 0xffffff80070dbe93 kernel`vnode_pager_data_request(mem_obj=0xffffff80145a48e8, offset=24576, length=<unavailable>, desired_access=<unavailable>, fault_info=<unavailable>) at bsd_vm.c:639 [opt]
frame #14: 0xffffff80070eb011 kernel`vm_fault_page [inlined] memory_object_data_request(memory_object=<unavailable>, offset=<unavailable>, length=4096, desired_access=1, fault_info=<unavailable>) at memory_object.c:2134 [opt]
frame #15: 0xffffff80070eaffb kernel`vm_fault_page(first_object=0xffffff8011d64a00, first_offset=4096, fault_type=1, must_be_resident=0, caller_lookup=0, protection=0xffffff806717be90, result_page=<unavailable>, top_page=<unavailable>, type_of_fault=<unavailable>, error_code=<unavailable>, no_zero_fill=<unavailable>, data_supply=0, fault_info=<unavailable>) at vm_fault.c:1770 [opt]
frame #16: 0xffffff80070ef82a kernel`vm_fault_internal(map=<unavailable>, vaddr=<unavailable>, caller_prot=1, change_wiring=<unavailable>, wire_tag=0, interruptible=2, caller_pmap=<unavailable>, caller_pmap_addr=<unavailable>, physpage_p=<unavailable>) at vm_fault.c:4610 [opt]
frame #17: 0xffffff8007188f92 kernel`user_trap [inlined] vm_fault(map=<unavailable>, fault_type=<unavailable>, change_wiring=0, wire_tag=0, interruptible=2, caller_pmap_addr=0) at vm_fault.c:3416 [opt]
frame #18: 0xffffff8007188f6f kernel`user_trap(saved_state=0xffffff800f07b7a0) at trap.c:1083 [opt]

My question is how it's possible to get memory fault on the file memory without open it first (kauth prevent open syscall). what system call can actually initial the kernel thread whose backtrace listed above ?

thanks

  • Does the process where this is happening have the dylib loaded? If so, I don't see how you're going to stop it. Either you let the process load the file, or you don't… – pmdj Jul 03 '18 at 15:04
  • I block this file from being accesses by other processes but my own. but it seems like it's still possible because kauth doesn't protect when you read the file using `vnode_pagein`... actually I even blocked the `mmap` using the unsupported KPI of mac policy `mpo_file_check_mmap` hook method... but the pagein still occurs, so i wonder what triggers it ? how can you read file memory without open it first and get file descriptor ? –  Jul 04 '18 at 07:06

1 Answers1

1

Presumably, the file is opened and mapped during loading of the host executable, which has it listed as a dependency directly or indirectly via another dependency. I don't know the specifics in excruciating detail, but presumably the code for loading the dynamic libraries is in dyld, but there might be some cooperation by the kernel. I know dyld does some amount of caching, so it's possible that the file isn't directly loaded but a cached version already in memory is mapped into the process instead. If true, this is presumably going to be tough to prevent. I'd check the dyld source and chase down how dynamic library dependencies of an executable are loaded, and see where, if anywhere, you might be able to intercept it.

pmdj
  • 22,018
  • 3
  • 52
  • 103
  • Hmmm... that's an interesting theory, So you mean that besides the file cache there's also cache which manage the loaded libraries in dyld. In my case this event was triggered from spindump which attempted to get the symbols used by the current sampling stack trace of my process that dynamically load this file. so I'm guessing it has some hidden way to get the file description. –  Jul 16 '18 at 08:25
  • 1
    That's a good article covering some dyld caching aspects especially the newest `dyld3` implementation: https://allegro.tech/2018/05/Static-linking-vs-dyld3.html – Kamil.S Jul 23 '18 at 10:45
  • @kamil.s, i just wonder if `spindump` actually tries to load the library in order to get the required symbols for the report. I will check this ... –  Jul 23 '18 at 11:08
  • The symtab is a normal link command (LC_SYMTAB) in the Mach-O file and as far as I’m aware is normally loaded into the process’s address space by dyld on load together with the rest of the file. So it should just be a matter of finding the address and reading the symbols from memory. – pmdj Jul 23 '18 at 11:42
  • @pmdj `LC_SYMTAB` was used in older Mach-O versions by `dyld`, nowadays it's driven by `LC_DYLD_INFO_ONLY` – Kamil.S Jul 23 '18 at 12:28