+
+/*
+ * CODESIGNING
+ * Routines to navigate code signing data structures in the kernel...
+ */
+
+extern int cs_debug;
+
+static boolean_t
+cs_valid_range(
+ const void *start,
+ const void *end,
+ const void *lower_bound,
+ const void *upper_bound)
+{
+ if (upper_bound < lower_bound ||
+ end < start) {
+ return FALSE;
+ }
+
+ if (start < lower_bound ||
+ end > upper_bound) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+hex_str(
+ const unsigned char *hash,
+ size_t len,
+ char *buf)
+{
+ unsigned int n;
+ for (n = 0; n < len; n++)
+ snprintf(buf + 2*n, 3, "%02.2x", hash[n]);
+}
+
+
+/*
+ * Locate the CodeDirectory from an embedded signature blob
+ */
+static const
+CS_CodeDirectory *findCodeDirectory(
+ const CS_SuperBlob *embedded,
+ char *lower_bound,
+ char *upper_bound)
+{
+ const CS_CodeDirectory *cd = NULL;
+
+ if (embedded &&
+ cs_valid_range(embedded, embedded + 1, lower_bound, upper_bound) &&
+ ntohl(embedded->magic) == CSMAGIC_EMBEDDED_SIGNATURE) {
+ const CS_BlobIndex *limit;
+ const CS_BlobIndex *p;
+
+ limit = &embedded->index[ntohl(embedded->count)];
+ if (!cs_valid_range(&embedded->index[0], limit,
+ lower_bound, upper_bound)) {
+ return NULL;
+ }
+ for (p = embedded->index; p < limit; ++p) {
+ if (ntohl(p->type) == CSSLOT_CODEDIRECTORY) {
+ const unsigned char *base;
+
+ base = (const unsigned char *)embedded;
+ cd = (const CS_CodeDirectory *)(base + ntohl(p->offset));
+ break;
+ }
+ }
+ } else {
+ /*
+ * Detached signatures come as a bare CS_CodeDirectory,
+ * without a blob.
+ */
+ cd = (const CS_CodeDirectory *) embedded;
+ }
+
+ if (cd &&
+ cs_valid_range(cd, cd + 1, lower_bound, upper_bound) &&
+ cs_valid_range(cd, (const char *) cd + ntohl(cd->length),
+ lower_bound, upper_bound) &&
+ cs_valid_range(cd, (const char *) cd + ntohl(cd->hashOffset),
+ lower_bound, upper_bound) &&
+ cs_valid_range(cd, (const char *) cd +
+ ntohl(cd->hashOffset) +
+ (ntohl(cd->nCodeSlots) * SHA1_RESULTLEN),
+ lower_bound, upper_bound) &&
+
+ ntohl(cd->magic) == CSMAGIC_CODEDIRECTORY) {
+ return cd;
+ }
+
+ // not found or not a valid code directory
+ return NULL;
+}
+
+
+/*
+ * Locating a page hash
+ */
+static const unsigned char *
+hashes(
+ const CS_CodeDirectory *cd,
+ unsigned page,
+ char *lower_bound,
+ char *upper_bound)
+{
+ const unsigned char *base, *top, *hash;
+ uint32_t nCodeSlots = ntohl(cd->nCodeSlots);
+
+ assert(cs_valid_range(cd, cd + 1, lower_bound, upper_bound));
+
+ if((ntohl(cd->version) >= CS_SUPPORTSSCATTER) && (ntohl(cd->scatterOffset))) {
+ /* Get first scatter struct */
+ const SC_Scatter *scatter = (const SC_Scatter*)
+ ((const char*)cd + ntohl(cd->scatterOffset));
+ uint32_t hashindex=0, scount, sbase=0;
+ /* iterate all scatter structs */
+ do {
+ if((const char*)scatter > (const char*)cd + ntohl(cd->length)) {
+ if(cs_debug) {
+ printf("CODE SIGNING: Scatter extends past Code Directory\n");
+ }
+ return NULL;
+ }
+
+ scount = ntohl(scatter->count);
+ uint32_t new_base = ntohl(scatter->base);
+
+ /* last scatter? */
+ if (scount == 0) {
+ return NULL;
+ }
+
+ if((hashindex > 0) && (new_base <= sbase)) {
+ if(cs_debug) {
+ printf("CODE SIGNING: unordered Scatter, prev base %d, cur base %d\n",
+ sbase, new_base);
+ }
+ return NULL; /* unordered scatter array */
+ }
+ sbase = new_base;
+
+ /* this scatter beyond page we're looking for? */
+ if (sbase > page) {
+ return NULL;
+ }
+
+ if (sbase+scount >= page) {
+ /* Found the scatter struct that is
+ * referencing our page */
+
+ /* base = address of first hash covered by scatter */
+ base = (const unsigned char *)cd + ntohl(cd->hashOffset) +
+ hashindex * SHA1_RESULTLEN;
+ /* top = address of first hash after this scatter */
+ top = base + scount * SHA1_RESULTLEN;
+ if (!cs_valid_range(base, top, lower_bound,
+ upper_bound) ||
+ hashindex > nCodeSlots) {
+ return NULL;
+ }
+
+ break;
+ }
+
+ /* this scatter struct is before the page we're looking
+ * for. Iterate. */
+ hashindex+=scount;
+ scatter++;
+ } while(1);
+
+ hash = base + (page - sbase) * SHA1_RESULTLEN;
+ } else {
+ base = (const unsigned char *)cd + ntohl(cd->hashOffset);
+ top = base + nCodeSlots * SHA1_RESULTLEN;
+ if (!cs_valid_range(base, top, lower_bound, upper_bound) ||
+ page > nCodeSlots) {
+ return NULL;
+ }
+ assert(page < nCodeSlots);
+
+ hash = base + page * SHA1_RESULTLEN;
+ }
+
+ if (!cs_valid_range(hash, hash + SHA1_RESULTLEN,
+ lower_bound, upper_bound)) {
+ hash = NULL;
+ }
+
+ return hash;
+}
+
+/*
+ * cs_validate_codedirectory
+ *
+ * Validate that pointers inside the code directory to make sure that
+ * all offsets and lengths are constrained within the buffer.
+ *
+ * Parameters: cd Pointer to code directory buffer
+ * length Length of buffer
+ *
+ * Returns: 0 Success
+ * EBADEXEC Invalid code signature
+ */
+
+static int
+cs_validate_codedirectory(const CS_CodeDirectory *cd, size_t length)
+{
+
+ if (length < sizeof(*cd))
+ return EBADEXEC;
+ if (ntohl(cd->magic) != CSMAGIC_CODEDIRECTORY)
+ return EBADEXEC;
+ if (cd->hashSize != SHA1_RESULTLEN)
+ return EBADEXEC;
+ if (cd->pageSize != PAGE_SHIFT)
+ return EBADEXEC;
+ if (cd->hashType != CS_HASHTYPE_SHA1)
+ return EBADEXEC;
+
+ if (length < ntohl(cd->hashOffset))
+ return EBADEXEC;
+
+ /* check that nSpecialSlots fits in the buffer in front of hashOffset */
+ if (ntohl(cd->hashOffset) / SHA1_RESULTLEN < ntohl(cd->nSpecialSlots))
+ return EBADEXEC;
+
+ /* check that codeslots fits in the buffer */
+ if ((length - ntohl(cd->hashOffset)) / SHA1_RESULTLEN < ntohl(cd->nCodeSlots))
+ return EBADEXEC;
+
+ if (ntohl(cd->version) >= CS_SUPPORTSSCATTER && cd->scatterOffset) {
+
+ if (length < ntohl(cd->scatterOffset))
+ return EBADEXEC;
+
+ SC_Scatter *scatter = (SC_Scatter *)
+ (((uint8_t *)cd) + ntohl(cd->scatterOffset));
+ uint32_t nPages = 0;
+
+ /*
+ * Check each scatter buffer, since we don't know the
+ * length of the scatter buffer array, we have to
+ * check each entry.
+ */
+ while(1) {
+ /* check that the end of each scatter buffer in within the length */
+ if (((const uint8_t *)scatter) + sizeof(scatter[0]) > (const uint8_t *)cd + length)
+ return EBADEXEC;
+ uint32_t scount = ntohl(scatter->count);
+ if (scount == 0)
+ break;
+ if (nPages + scount < nPages)
+ return EBADEXEC;
+ nPages += scount;
+ scatter++;
+
+ /* XXX check that basees doesn't overlap */
+ /* XXX check that targetOffset doesn't overlap */
+ }
+#if 0 /* rdar://12579439 */
+ if (nPages != ntohl(cd->nCodeSlots))
+ return EBADEXEC;
+#endif
+ }
+
+ if (length < ntohl(cd->identOffset))
+ return EBADEXEC;
+
+ /* identifier is NUL terminated string */
+ if (cd->identOffset) {
+ uint8_t *ptr = (uint8_t *)cd + ntohl(cd->identOffset);
+ if (memchr(ptr, 0, length - ntohl(cd->identOffset)) == NULL)
+ return EBADEXEC;
+ }
+
+ return 0;
+}
+
+/*
+ *
+ */
+
+static int
+cs_validate_blob(const CS_GenericBlob *blob, size_t length)
+{
+ if (length < sizeof(CS_GenericBlob) || length < ntohl(blob->length))
+ return EBADEXEC;
+ return 0;
+}
+
+/*
+ * cs_validate_csblob
+ *
+ * Validate that superblob/embedded code directory to make sure that
+ * all internal pointers are valid.
+ *
+ * Will validate both a superblob csblob and a "raw" code directory.
+ *
+ *
+ * Parameters: buffer Pointer to code signature
+ * length Length of buffer
+ * rcd returns pointer to code directory
+ *
+ * Returns: 0 Success
+ * EBADEXEC Invalid code signature
+ */
+
+static int
+cs_validate_csblob(const uint8_t *addr, size_t length,
+ const CS_CodeDirectory **rcd)
+{
+ const CS_GenericBlob *blob = (const CS_GenericBlob *)(void *)addr;
+ int error;
+
+ *rcd = NULL;
+
+ error = cs_validate_blob(blob, length);
+ if (error)
+ return error;
+
+ length = ntohl(blob->length);
+
+ if (ntohl(blob->magic) == CSMAGIC_EMBEDDED_SIGNATURE) {
+ const CS_SuperBlob *sb = (const CS_SuperBlob *)blob;
+ uint32_t n, count = ntohl(sb->count);
+
+ if (length < sizeof(CS_SuperBlob))
+ return EBADEXEC;
+
+ /* check that the array of BlobIndex fits in the rest of the data */
+ if ((length - sizeof(CS_SuperBlob)) / sizeof(CS_BlobIndex) < count)
+ return EBADEXEC;
+
+ /* now check each BlobIndex */
+ for (n = 0; n < count; n++) {
+ const CS_BlobIndex *blobIndex = &sb->index[n];
+ if (length < ntohl(blobIndex->offset))
+ return EBADEXEC;
+
+ const CS_GenericBlob *subBlob =
+ (const CS_GenericBlob *)(void *)(addr + ntohl(blobIndex->offset));
+
+ size_t subLength = length - ntohl(blobIndex->offset);
+
+ if ((error = cs_validate_blob(subBlob, subLength)) != 0)
+ return error;
+ subLength = ntohl(subBlob->length);
+
+ /* extra validation for CDs, that is also returned */
+ if (ntohl(blobIndex->type) == CSSLOT_CODEDIRECTORY) {
+ const CS_CodeDirectory *cd = (const CS_CodeDirectory *)subBlob;
+ if ((error = cs_validate_codedirectory(cd, subLength)) != 0)
+ return error;
+ *rcd = cd;
+ }
+ }
+
+ } else if (ntohl(blob->magic) == CSMAGIC_CODEDIRECTORY) {
+
+ if ((error = cs_validate_codedirectory((const CS_CodeDirectory *)(void *)addr, length)) != 0)
+ return error;
+ *rcd = (const CS_CodeDirectory *)blob;
+ } else {
+ return EBADEXEC;
+ }
+
+ if (*rcd == NULL)
+ return EBADEXEC;
+
+ return 0;
+}
+
+/*
+ * cs_find_blob_bytes
+ *
+ * Find an blob from the superblob/code directory. The blob must have
+ * been been validated by cs_validate_csblob() before calling
+ * this. Use cs_find_blob() instead.
+ *
+ * Will also find a "raw" code directory if its stored as well as
+ * searching the superblob.
+ *
+ * Parameters: buffer Pointer to code signature
+ * length Length of buffer
+ * type type of blob to find
+ * magic the magic number for that blob
+ *
+ * Returns: pointer Success
+ * NULL Buffer not found
+ */
+
+static const CS_GenericBlob *
+cs_find_blob_bytes(const uint8_t *addr, size_t length, uint32_t type, uint32_t magic)
+{
+ const CS_GenericBlob *blob = (const CS_GenericBlob *)(void *)addr;
+
+ if (ntohl(blob->magic) == CSMAGIC_EMBEDDED_SIGNATURE) {
+ const CS_SuperBlob *sb = (const CS_SuperBlob *)blob;
+ size_t n, count = ntohl(sb->count);
+
+ for (n = 0; n < count; n++) {
+ if (ntohl(sb->index[n].type) != type)
+ continue;
+ uint32_t offset = ntohl(sb->index[n].offset);
+ if (length - sizeof(const CS_GenericBlob) < offset)
+ return NULL;
+ blob = (const CS_GenericBlob *)(void *)(addr + offset);
+ if (ntohl(blob->magic) != magic)
+ continue;
+ return blob;
+ }
+ } else if (type == CSSLOT_CODEDIRECTORY
+ && ntohl(blob->magic) == CSMAGIC_CODEDIRECTORY
+ && magic == CSMAGIC_CODEDIRECTORY)
+ return blob;
+ return NULL;
+}
+
+
+static const CS_GenericBlob *
+cs_find_blob(struct cs_blob *csblob, uint32_t type, uint32_t magic)
+{
+ if ((csblob->csb_flags & CS_VALID) == 0)
+ return NULL;
+ return cs_find_blob_bytes((const uint8_t *)csblob->csb_mem_kaddr, csblob->csb_mem_size, type, magic);
+}
+
+static const uint8_t *
+cs_find_special_slot(const CS_CodeDirectory *cd, uint32_t slot)
+{
+ /* there is no zero special slot since that is the first code slot */
+ if (ntohl(cd->nSpecialSlots) < slot || slot == 0)
+ return NULL;
+
+ return ((const uint8_t *)cd + ntohl(cd->hashOffset) - (SHA1_RESULTLEN * slot));
+}
+
+/*
+ * CODESIGNING
+ * End of routines to navigate code signing data structures in the kernel.
+ */
+
+/*
+ * ENTITLEMENTS
+ * Routines to navigate entitlements in the kernel.
+ */
+
+/* Retrieve the entitlements blob for a process.
+ * Returns:
+ * EINVAL no text vnode associated with the process
+ * EBADEXEC invalid code signing data
+ * 0 no error occurred
+ *
+ * On success, out_start and out_length will point to the
+ * entitlements blob if found; or will be set to NULL/zero
+ * if there were no entitlements.
+ */
+
+static uint8_t sha1_zero[SHA1_RESULTLEN] = { 0 };
+
+int
+cs_entitlements_blob_get(proc_t p, void **out_start, size_t *out_length)
+{
+ uint8_t computed_hash[SHA1_RESULTLEN];
+ const CS_GenericBlob *entitlements;
+ const CS_CodeDirectory *code_dir;
+ struct cs_blob *csblob;
+ const uint8_t *embedded_hash;
+ SHA1_CTX context;
+
+ *out_start = NULL;
+ *out_length = 0;
+
+ if (NULL == p->p_textvp)
+ return EINVAL;
+
+ if ((csblob = ubc_cs_blob_get(p->p_textvp, -1, p->p_textoff)) == NULL)
+ return 0;
+
+ if ((code_dir = (const CS_CodeDirectory *)cs_find_blob(csblob, CSSLOT_CODEDIRECTORY, CSMAGIC_CODEDIRECTORY)) == NULL)
+ return 0;
+
+ entitlements = cs_find_blob(csblob, CSSLOT_ENTITLEMENTS, CSMAGIC_EMBEDDED_ENTITLEMENTS);
+ embedded_hash = cs_find_special_slot(code_dir, CSSLOT_ENTITLEMENTS);
+
+ if (embedded_hash == NULL) {
+ if (entitlements)
+ return EBADEXEC;
+ return 0;
+ } else if (entitlements == NULL && memcmp(embedded_hash, sha1_zero, SHA1_RESULTLEN) != 0) {
+ return EBADEXEC;
+ }
+
+ SHA1Init(&context);
+ SHA1Update(&context, entitlements, ntohl(entitlements->length));
+ SHA1Final(computed_hash, &context);
+ if (memcmp(computed_hash, embedded_hash, SHA1_RESULTLEN) != 0)
+ return EBADEXEC;
+
+ *out_start = (void *)entitlements;
+ *out_length = ntohl(entitlements->length);
+
+ return 0;
+}
+
+/* Retrieve the codesign identity for a process.
+ * Returns:
+ * NULL an error occured
+ * string the cs_identity
+ */
+
+const char *
+cs_identity_get(proc_t p)
+{
+ const CS_CodeDirectory *code_dir;
+ struct cs_blob *csblob;
+
+ if (NULL == p->p_textvp)
+ return NULL;
+
+ if ((csblob = ubc_cs_blob_get(p->p_textvp, -1, p->p_textoff)) == NULL)
+ return NULL;
+
+ if ((code_dir = (const CS_CodeDirectory *)cs_find_blob(csblob, CSSLOT_CODEDIRECTORY, CSMAGIC_CODEDIRECTORY)) == NULL)
+ return NULL;
+
+ if (code_dir->identOffset == 0)
+ return NULL;
+
+ return ((const char *)code_dir) + ntohl(code_dir->identOffset);
+}
+
+
+
+/* Retrieve the codesign blob for a process.
+ * Returns:
+ * EINVAL no text vnode associated with the process
+ * 0 no error occurred
+ *
+ * On success, out_start and out_length will point to the
+ * cms blob if found; or will be set to NULL/zero
+ * if there were no blob.
+ */
+
+int
+cs_blob_get(proc_t p, void **out_start, size_t *out_length)
+{
+ struct cs_blob *csblob;
+
+ *out_start = NULL;
+ *out_length = 0;
+
+ if (NULL == p->p_textvp)
+ return EINVAL;
+
+ if ((csblob = ubc_cs_blob_get(p->p_textvp, -1, p->p_textoff)) == NULL)
+ return 0;
+
+ *out_start = (void *)csblob->csb_mem_kaddr;
+ *out_length = csblob->csb_mem_size;
+
+ return 0;
+}
+
+uint8_t *
+cs_get_cdhash(struct proc *p)
+{
+ struct cs_blob *csblob;
+
+ if (NULL == p->p_textvp)
+ return NULL;
+
+ if ((csblob = ubc_cs_blob_get(p->p_textvp, -1, p->p_textoff)) == NULL)
+ return NULL;
+
+ return csblob->csb_sha1;
+}
+
+/*
+ * ENTITLEMENTS
+ * End of routines to navigate entitlements in the kernel.
+ */
+
+
+