4

I'm building a kext for an extra layer of security on OS X (built around KAtuh). I'm using a client in userspace that connects to the kext over sockets (as advised by Apple), and basically controls the kext. Because the product is supposed to provide extra security for OS X, it is important that it is "as secure as possible" against attacks. One attack vector is the following: A malicious process impersonates the client and sends malicious control data to the kext, disabling the security mechanism.. I want to prevent this by doing authentication upon connection. Here are my solutions:

  • Run the client as root, use CTL_FLAG_PRIVILEGED flag to ensure only root clients can connect to the kext. I'm not sure if I want to have my client run in privileged mode (again: extra attack vector).

  • Let the kext be connected to only one client. However, this is easily by-passable.

Ideally, I want to verify the identity of the client that connects through static int ctl_connect(kern_ctl_ref ctl_ref, struct sockaddr_ctl *sac, void **unitinfo). How can I do this?

I can also do packet authentication in static int ctl_set(kern_ctl_ref ctl_ref, u_int32_t unit, void *unitinfo, int opt, void *data, size_t len), however, I would have to come up with a dynamic shared secret. I was thinking about secret = SHA256(getUDID()), but AFAIK there are no crypto KPI's available, neither a way to getUDID() from kernelspace.

Are there any other idea's on doing "proper" authentication of clients?

Vis
  • 301
  • 1
  • 10
  • I'm curious about the best way of doing this (and authorising other kernel calls) too. One option is to set up a dedicated unprivileged user for your daemon, and check the UUID of the EUID of the process trying to connect. I'm not sure that's necessarily the way Apple wants you to do it though. In any case, I'll add a bounty to this as soon as I can, and maybe file a DTS request if nobody answers. – pmdj Feb 03 '16 at 11:08
  • @pmdj Do you maybe know a way to grab the MAC-address from kernelspace of either the Ethernet or WiFi card and use that as a dynamic auth code? – Vis Feb 08 '16 at 08:24
  • I'm not sure how safe it is, but you can find the IOEthernetController and/or IOEthernetInterface instances of all the ethernet devices in the system, and query them. I'm not really sure how that would help for solving this issue though as MAC addresses are easy to get hold of. – pmdj Feb 08 '16 at 12:14
  • FYI, I have filed a DTS request about this, as it would be useful to know on 2 of our projects as well. I suspect the solution is probably to only allow root processes though. :-/ – pmdj Feb 10 '16 at 11:52
  • One possibility is to register a KAuth listener for `KAUTH_FILEOP_EXEC` when the kext loads up and keep track of all processes that execute; their procname, binary path, pid, ppid etc. Then some path validation in `ctl_connect` based on `proc_selfname()` and `proc_selfpid()` and some other public KAuth functions. Problem however is that the kext needs to be loaded early in the boot chain. – Vis Feb 11 '16 at 16:14
  • You can get the name without a kauth listener, but faking a name is pretty easy. You'd really want to check the codesigning signature for your own certificate or something like that. I don't think that's possible with public APIs. The MAC framework (unsupported API/unstable ABI) has a mpo_vnode_check_signature_t policy callback which appears to include the signature. I don't know if that's called under the correct circumstances though. (No word back from DTS yet.) – pmdj Feb 11 '16 at 19:48
  • You could perform your own checksum on the binary if you have the path? Import SHA-256 in you kext and sha256(path/to/process/binary). I have not tried this yet though... – Vis Feb 12 '16 at 13:45
  • You could, but it's fragile, because any mismatches between kext and daemon version will break everything. That's why you'd want to check that it was signed with a specific certificate, and the signature is valid. – pmdj Feb 12 '16 at 16:56

2 Answers2

1

I have asked Apple's Developer Tech Support this question, and they have said the only supported way to restrict user client access to kexts is to distinguish between root and non-root processes.

Personally, for the purposes of reducing the attack surface, it would indeed be useful to drop user client privileges. The Linux way of checking for a specific group membership seems like it should work on OS X too. (For example, you typically need to be part of the 'kvm' group to use the KVM virtualisation technology on Linux.) The only way to become a member of the group is via root privileges (setting up the Launch Daemon's GroupName requires root privileges) so this should be secure. I have yet to try this myself, but I've got 2 projects where this would make sense so I'll give it a go and will update this answer with my findings.

pmdj
  • 22,018
  • 3
  • 52
  • 103
  • Thanks for asking Apple's dev tech support! At least we now know what Apple expects us to do... – Vis Feb 28 '16 at 11:31
  • They did say we should file an enhancement request radar. I'll try to do so in the next 2 weeks or so (workload is a bit crazy right now) but the more of us there are requesting it, the more likely it is it'll be added. If you do file a request, post your radar number so I can reference it. (I'll post ours if I get around to it before you.) – pmdj Feb 28 '16 at 16:17
  • I have filed this under 25165008. – Vis Mar 15 '16 at 11:36
1

Apple has implemented functionality in the AMFI kext (<sys/codesign.h> header) that can be used to obtain the TeamID from a signed binary. If this header would be public, this is exactly what could be used to authenticate the client process connecting to the kext.

/*
 * Function: csfg_get_teamid
 *
 * Description: This returns a pointer to
 *      the teamid for the fileglob fg
 */
const char *
csfg_get_teamid(struct fileglob *fg)
{
    struct ubc_info *uip;
    const char *str = NULL;
    vnode_t vp;

    if (FILEGLOB_DTYPE(fg) != DTYPE_VNODE)
        return NULL;

    vp = (struct vnode *)fg->fg_data;
    if (vp == NULL)
        return NULL;

    vnode_lock(vp);
    if (!UBCINFOEXISTS(vp))
        goto out;

    uip = vp->v_ubcinfo;
    if (uip == NULL)
        goto out;

    if (uip->cs_blobs == NULL)
        goto out;

    /* It is OK to extract the teamid from the first blob
       because all blobs of a vnode must have the same teamid */    
    str = uip->cs_blobs->csb_teamid;
out:
    vnode_unlock(vp);

    return str;
}
Vis
  • 301
  • 1
  • 10