cskernel.cpp   [plain text]


/*
 * Copyright (c) 2006-2007 Apple Inc. All Rights Reserved.
 * 
 * @APPLE_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
 * compliance with the License. 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,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * 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_LICENSE_HEADER_END@
 */

//
// cskernel - Kernel implementation of the Code Signing Host Interface.
//
// The kernel host currently supports only UNIX processes as guests.
// It tracks then by their pid. Perhaps one day we'll get a more stable
// means of tracking processes that doesn't involve reusing identifiers.
//
// The kernel host could represent non-process guests one day. One candidate
// are Kernel Extensions.
//
#include "cskernel.h"
#include "csprocess.h"
#include "kerneldiskrep.h"
#include "machorep.h"
#include <libproc.h>
#include <sys/codesign.h>
#include <sys/param.h>	// MAXPATHLEN

namespace Security {
namespace CodeSigning {


//
// The running-kernel singletons
//
ModuleNexus<KernelCode::Globals> KernelCode::globals;

KernelCode::Globals::Globals()
{
	code = new KernelCode;
	staticCode = new KernelStaticCode;
}

KernelCode::KernelCode()
	: SecCode(NULL)
{
}

KernelStaticCode::KernelStaticCode()
	: SecStaticCode(new KernelDiskRep())
{
}


//
// Identify our guests (UNIX processes) by attribute.
// The only supported lookup attribute is currently the pid. (We could support
// task ports, but those can easily be mapped to pids.)
// Note that we don't actually validate the pid here; if it's invalid, we'll notice
// when we try to ask the kernel about it later.
//
SecCode *KernelCode::locateGuest(CFDictionaryRef attributes)
{
	if (CFTypeRef attr = CFDictionaryGetValue(attributes, kSecGuestAttributePid)) {
		if (CFDictionaryGetCount(attributes) != 1)
			MacOSError::throwMe(errSecCSUnsupportedGuestAttributes); // had more
		if (CFGetTypeID(attr) == CFNumberGetTypeID())
			return (new ProcessCode(cfNumber<pid_t>(CFNumberRef(attr))))->retain();
		MacOSError::throwMe(errSecCSInvalidAttributeValues);
	} else
		MacOSError::throwMe(errSecCSUnsupportedGuestAttributes);
}


//
// We map guests to disk by calling a kernel service.
// It is here that we verify that our user-space concept of the code identity
// matches the kernel's idea (to defeat just-in-time switching attacks).
//
SecStaticCode *KernelCode::identifyGuest(SecCode *iguest, CFDataRef *cdhash)
{
	if (ProcessCode *guest = dynamic_cast<ProcessCode *>(iguest)) {
		char path[2 * MAXPATHLEN];	// reasonable upper limit
		if (::proc_pidpath(guest->pid(), path, sizeof(path))) {
			off_t offset;
			csops(guest, CS_OPS_PIDOFFSET, &offset, sizeof(offset));
			SecPointer<SecStaticCode> code = new ProcessStaticCode(DiskRep::bestGuess(path, offset));
			CODESIGN_GUEST_IDENTIFY_PROCESS(guest, guest->pid(), code);
			if (cdhash) {
				SHA1::Digest kernelHash;
				if (::csops(guest->pid(), CS_OPS_CDHASH, kernelHash, sizeof(kernelHash)) == -1)
					switch (errno) {
					case EBADEXEC:		// means "no CodeDirectory hash for this program"
						*cdhash = NULL;
						break;
					case ESRCH:
						MacOSError::throwMe(errSecCSNoSuchCode);
					default:
						UnixError::throwMe();
					}
				else	// succeeded
					*cdhash = makeCFData(kernelHash, sizeof(kernelHash));
				CODESIGN_GUEST_CDHASH_PROCESS(guest, kernelHash, sizeof(kernelHash));
			}
			return code.yield();
		} else
			UnixError::throwMe();
	}
	MacOSError::throwMe(errSecCSNoSuchCode);
}


//
// We obtain the guest's status by asking the kernel
//
SecCodeStatus KernelCode::getGuestStatus(SecCode *iguest)
{
	if (ProcessCode *guest = dynamic_cast<ProcessCode *>(iguest)) {
		uint32_t pFlags;
		csops(guest, CS_OPS_STATUS, &pFlags);
		secdebug("kcode", "guest %p(%d) kernel status 0x%x", guest, guest->pid(), pFlags);
		return pFlags;
	} else
		MacOSError::throwMe(errSecCSNoSuchCode);
}


//
// We tell the kernel to make status changes
//
void KernelCode::changeGuestStatus(SecCode *iguest, SecCodeStatusOperation operation, CFDictionaryRef arguments)
{
	if (ProcessCode *guest = dynamic_cast<ProcessCode *>(iguest))
		switch (operation) {
		case kSecCodeOperationNull:
			break;
		case kSecCodeOperationInvalidate:
			csops(guest, CS_OPS_MARKINVALID);
			break;
		case kSecCodeOperationSetHard:
			csops(guest, CS_OPS_MARKHARD);
			break;
		case kSecCodeOperationSetKill:
			csops(guest, CS_OPS_MARKKILL);
			break;
		default:
			MacOSError::throwMe(errSecCSInvalidOperation);
		}
	else
		MacOSError::throwMe(errSecCSNoSuchCode);
}


//
// The StaticCode for the running kernel is explicit.
// We can't ask our own host for it, naturally.
//
void KernelCode::identify()
{
	mStaticCode.take(globals().staticCode->retain());
	// the kernel isn't currently signed, so we don't get a cdHash for it
}


//
// Interface to kernel csops() system call.
//
void KernelCode::csops(ProcessCode *proc, unsigned int op, void *addr, size_t length)
{
	if (::csops(proc->pid(), op, addr, length) == -1) {
		switch (errno) {
		case ESRCH:
			MacOSError::throwMe(errSecCSNoSuchCode);
		default:
			UnixError::throwMe();
		}
	}
}


} // CodeSigning
} // Security