/* * Copyright (c) 2013, The Linux Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include /* Service IDs */ #define SCM_SVC_SSD 0x07 /* Service specific command IDs */ #define SSD_PARSE_MD_ID 0x06 #define SSD_DECRYPT_IMG_FRAG_ID 0x07 /* SSD parsing status messages from TZ */ #define SSD_PMD_ENCRYPTED 0 #define SSD_PMD_NOT_ENCRYPTED 1 #define SSD_PMD_PARSING_INCOMPLETE 6 #define SSD_HEADER_MIN_SIZE 128 #define MULTIPLICATION_FACTOR 2 #define SMCMOD_DECRYPT_REQ_OP_METADATA 1 #define SMCMOD_DECRYPT_REQ_OP_IMG_FRAG 2 struct smcmod_decrypt_req { uint32_t service_id; /* in */ uint32_t command_id; /* in */ uint32_t operation; /* in */ union { struct { uint32_t len; uint32_t ion_fd; } metadata; struct { uint32_t ctx_id; uint32_t last_frag; uint32_t frag_len; uint32_t ion_fd; uint32_t offset; } img_frag; } request; union { struct { uint32_t status; uint32_t ctx_id; uint32_t end_offset; } metadata; struct { uint32_t status; } img_frag; } response; }; #define SMCMOD_IOC_MAGIC 0x97 #define SMCMOD_IOCTL_DECRYPT_CMD \ _IOWR(SMCMOD_IOC_MAGIC, 37, struct smcmod_decrypt_req) struct ion_buf_handle { unsigned char *buffer; uint32_t buffer_len; int ion_fd; int ifd_data_fd; struct ion_handle_data ion_alloc_handle; }; static int ion_memalloc(struct ion_buf_handle *buf, uint32_t size, uint32_t heap) { struct ion_allocation_data alloc_data; struct ion_fd_data fd_data; unsigned char *va; struct ion_handle_data handle_data; int ion_fd; int rc; ion_fd = open("/dev/ion", O_RDONLY); if (ion_fd < 0) { fprintf(stderr, "Cannot open ION device (%s)\n", strerror(errno)); return -1; } alloc_data.len = (size + 4095) & ~4095; alloc_data.align = 4096; alloc_data.flags = 0; alloc_data.heap_mask = ION_HEAP(heap); /* Set the buffers to be uncached */ alloc_data.flags = 0; rc = ioctl(ion_fd, ION_IOC_ALLOC, &alloc_data); if (rc) { fprintf(stderr, "ION buffer allocation failed (%s)\n", strerror(errno)); goto alloc_fail; } if (alloc_data.handle) { fd_data.handle = alloc_data.handle; } else { fprintf(stderr, "ION alloc data returned NULL\n"); rc = -1; goto alloc_fail; } rc = ioctl(ion_fd, ION_IOC_MAP, &fd_data); if (rc) { fprintf(stderr, "ION map call failed(%s)\n", strerror(errno)); goto ioctl_fail; } va = mmap(NULL, alloc_data.len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_data.fd, 0); if (va == MAP_FAILED) { fprintf(stderr, "ION memory map failed (%s)\n", strerror(errno)); rc = -1; goto map_fail; } buf->ion_fd = ion_fd; buf->ifd_data_fd = fd_data.fd; buf->buffer = va; buf->ion_alloc_handle.handle = alloc_data.handle; buf->buffer_len = alloc_data.len; memset(buf->buffer, 0, buf->buffer_len); return 0; map_fail: ioctl_fail: handle_data.handle = alloc_data.handle; if (buf->ifd_data_fd) close(buf->ifd_data_fd); rc = ioctl(ion_fd, ION_IOC_FREE, &handle_data); if (rc) fprintf(stderr, "ION free failed (%s)\n", strerror(errno)); alloc_fail: if (ion_fd >= 0) close(ion_fd); buf->ion_fd = -1; return rc; } static int ion_memfree(struct ion_buf_handle *handle) { struct ion_handle_data handle_data; int ret; ret = munmap(handle->buffer, (handle->buffer_len + 4095) & ~4095); if (ret) fprintf(stderr, "munmap failed (%s)\n", strerror(errno)); handle_data.handle = handle->ion_alloc_handle.handle; close(handle->ifd_data_fd); ret = ioctl(handle->ion_fd, ION_IOC_FREE, &handle_data); if (ret) fprintf(stderr, "ION free failed (%s)\n", strerror(errno)); close(handle->ion_fd); return ret; } static int ion_buffer_clean_inval(struct ion_buf_handle *buf, uint32_t cmd) { struct ion_flush_data data; int rc; data.fd = buf->ifd_data_fd; data.vaddr = buf->buffer; data.length = buf->buffer_len; data.offset = 0; data.handle = buf->ion_alloc_handle.handle; rc = ioctl(buf->ion_fd, cmd, &data); if (rc < 0) fprintf(stderr, "clean_inval cache failed (%s)\n", strerror(errno)); return rc; } static int is_encrypted(int smcmod_fd, struct ion_buf_handle *buf, uint32_t len, uint32_t *ctx_id, uint32_t *end_offset) { struct smcmod_decrypt_req req; uint32_t status; int ret; req.service_id = SCM_SVC_SSD; req.command_id = SSD_PARSE_MD_ID; req.operation = SMCMOD_DECRYPT_REQ_OP_METADATA; req.request.metadata.len = (len >= SSD_HEADER_MIN_SIZE) ? SSD_HEADER_MIN_SIZE : len; req.request.metadata.ion_fd = buf->ifd_data_fd; do { ret = ioctl(smcmod_fd, SMCMOD_IOCTL_DECRYPT_CMD, &req); if (ret < 0) fprintf(stderr, "%s: ioctl ret=%d, %s\n", __func__, ret, strerror(errno)); status = req.response.metadata.status; if (!ret && (status == SSD_PMD_PARSING_INCOMPLETE)) { req.request.metadata.len *= MULTIPLICATION_FACTOR; continue; } else { break; } } while (1); if (!ret) { if (status == SSD_PMD_ENCRYPTED) { *ctx_id = req.response.metadata.ctx_id; *end_offset = req.response.metadata.end_offset; ret = 1; } else { fprintf(stderr, "Image is not encrypted (response status %d)\n", status); } } else { fprintf(stderr, "%s: call failed\n", __func__); } return ret; } static int decrypt(int smcmod_fd, struct ion_buf_handle *buf, uint32_t len, uint32_t md_ctx, uint32_t offset) { struct smcmod_decrypt_req req; int ret; req.service_id = SCM_SVC_SSD; req.command_id = SSD_DECRYPT_IMG_FRAG_ID; req.operation = SMCMOD_DECRYPT_REQ_OP_IMG_FRAG; req.request.img_frag.ctx_id = md_ctx; req.request.img_frag.last_frag = 1; req.request.img_frag.ion_fd = buf->ifd_data_fd; req.request.img_frag.frag_len = len - offset; req.request.img_frag.offset = offset; ret = ioctl(smcmod_fd, SMCMOD_IOCTL_DECRYPT_CMD, &req); if (ret < 0) { fprintf(stderr, "decrypt ioctl failed (%s)\n", strerror(errno)); return ret; } return 0; } static int save_file(const char *fname, unsigned char *buf, size_t len) { FILE *file; size_t written; file = fopen(fname, "wb"); if (!file) { fprintf(stderr, "Failed to open %s (%s)\n", fname, strerror(errno)); return -errno; } written = fwrite(buf, len, 1, file); if (written != 1) { fclose(file); fprintf(stderr, "Failed to write %s (%s)\n", fname, strerror(errno)); return -errno; } fflush(file); fclose(file); fprintf(stdout, "%s written %d bytes\n", fname, len); return 0; } int decrypt_image(const char *src_file, const char *dst_file) { int ret = -1; uint32_t md_ctx = 0, offset = 0; uint32_t fsize = 0; FILE *file = NULL; struct ion_buf_handle ionbuf; int smcmod_fd = -1; int qseecom_fd = -1; size_t read; memset(&ionbuf, 0, sizeof(ionbuf)); ionbuf.ion_fd = -1; qseecom_fd = open("/dev/qseecom", O_RDWR); if (qseecom_fd < 0) { fprintf(stderr, "Failed to open /dev/qseecom device (%s)\n", strerror(errno)); goto exit; } smcmod_fd = open("/dev/smcmod", O_RDWR); if (smcmod_fd < 0) { fprintf(stderr, "Failed to open /dev/smcmod device (%s)\n", strerror(errno)); goto exit; } file = fopen(src_file, "rb"); if (!file) { fprintf(stderr, "Failed to open %s (%s)\n", src_file, strerror(errno)); goto exit; } fseek(file, 0, SEEK_END); fsize = ftell(file); fseek(file, 0, SEEK_SET); ret = ion_memalloc(&ionbuf, fsize, ION_PIL1_HEAP_ID); if (ret) goto exit; read = fread(ionbuf.buffer, fsize, 1, file); if (read != 1) { fprintf(stderr, "Failed to read %s (%s)\n", src_file, strerror(errno)); ret = -errno; goto exit; } ret = ion_buffer_clean_inval(&ionbuf, ION_IOC_CLEAN_INV_CACHES); if (ret < 0) goto exit; ret = ioctl(qseecom_fd, QSEECOM_IOCTL_PERF_ENABLE_REQ); if (ret < 0) goto exit; ret = is_encrypted(smcmod_fd, &ionbuf, fsize, &md_ctx, &offset); if (ret < 0) goto exit; if (ret == 1) { fprintf(stdout, "decrypting %s ...\n", src_file); ret = decrypt(smcmod_fd, &ionbuf, fsize, md_ctx, offset); if (ret < 0) goto exit; ion_buffer_clean_inval(&ionbuf, ION_IOC_INV_CACHES); ret = save_file(dst_file, ionbuf.buffer + offset, fsize - offset); if (ret < 0) goto exit; fprintf(stdout, "decrypting done!\n"); } exit: if (ionbuf.ion_fd >= 0) ion_memfree(&ionbuf); if (qseecom_fd >= 0) { ioctl(qseecom_fd, QSEECOM_IOCTL_PERF_DISABLE_REQ); close(qseecom_fd); } if (smcmod_fd >= 0) close(smcmod_fd); if (file) fclose(file); return ret; }