diff --git a/trusty/storage/proxy/Android.mk b/trusty/storage/proxy/Android.mk new file mode 100644 index 000000000..9fc73d3a2 --- /dev/null +++ b/trusty/storage/proxy/Android.mk @@ -0,0 +1,39 @@ +# +# Copyright (C) 2016 The Android Open-Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := storageproxyd + +LOCAL_SRC_FILES := \ + ipc.c \ + rpmb.c \ + storage.c \ + proxy.c + +LOCAL_CLFAGS = -Wall -Werror + +LOCAL_SHARED_LIBRARIES := \ + liblog \ + +LOCAL_STATIC_LIBRARIES := \ + libtrustystorageinterface \ + libtrusty + +include $(BUILD_EXECUTABLE) + diff --git a/trusty/storage/proxy/ipc.c b/trusty/storage/proxy/ipc.c new file mode 100644 index 000000000..b4748e209 --- /dev/null +++ b/trusty/storage/proxy/ipc.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ipc.h" +#include "log.h" + +#define MAX_RECONNECT_RETRY_COUNT 5 +#define TRUSTY_RECONNECT_TIMEOUT_SEC 5 + +static int tipc_fd = -1; + +int ipc_connect(const char *device, const char *port) +{ + int rc; + uint retry_cnt = 0; + + assert(tipc_fd == -1); + + while(true) { + rc = tipc_connect(device, port); + if (rc >= 0) + break; + + ALOGE("failed (%d) to connect to storage server\n", rc); + if (++retry_cnt > MAX_RECONNECT_RETRY_COUNT) { + ALOGE("max number of reconnect retries (%d) has been reached\n", + retry_cnt); + return -1; + } + sleep(TRUSTY_RECONNECT_TIMEOUT_SEC); + } + tipc_fd = rc; + return 0; +} + +void ipc_disconnect(void) +{ + assert(tipc_fd >= 0); + + tipc_close(tipc_fd); + tipc_fd = -1; +} + +ssize_t ipc_get_msg(struct storage_msg *msg, void *req_buf, size_t req_buf_len) +{ + ssize_t rc; + struct iovec iovs[2] = {{msg, sizeof(*msg)}, {req_buf, req_buf_len}}; + + assert(tipc_fd >= 0); + + rc = readv(tipc_fd, iovs, 2); + if (rc < 0) { + ALOGE("failed to read request: %s\n", strerror(errno)); + return rc; + } + + /* check for minimum size */ + if ((size_t)rc < sizeof(*msg)) { + ALOGE("message is too short (%zu bytes received)\n", rc); + return -1; + } + + /* check for message completeness */ + if (msg->size != (uint32_t)rc) { + ALOGE("inconsistent message size [cmd=%d] (%u != %u)\n", + msg->cmd, msg->size, (uint32_t)rc); + return -1; + } + + return rc - sizeof(*msg); +} + +int ipc_respond(struct storage_msg *msg, void *out, size_t out_size) +{ + ssize_t rc; + struct iovec iovs[2] = {{msg, sizeof(*msg)}, {out, out_size}}; + + assert(tipc_fd >= 0); + + msg->cmd |= STORAGE_RESP_BIT; + + rc = writev(tipc_fd, iovs, out ? 2 : 1); + if (rc < 0) { + ALOGE("error sending response 0x%x: %s\n", + msg->cmd, strerror(errno)); + return -1; + } + + return 0; +} + + diff --git a/trusty/storage/proxy/ipc.h b/trusty/storage/proxy/ipc.h new file mode 100644 index 000000000..2e366bbb9 --- /dev/null +++ b/trusty/storage/proxy/ipc.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +int ipc_connect(const char *device, const char *service_name); +void ipc_disconnect(void); +ssize_t ipc_get_msg(struct storage_msg *msg, void *req_buf, size_t req_buf_len); +int ipc_respond(struct storage_msg *msg, void *out, size_t out_size); diff --git a/trusty/storage/proxy/log.h b/trusty/storage/proxy/log.h new file mode 100644 index 000000000..471cb500f --- /dev/null +++ b/trusty/storage/proxy/log.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "storageproxyd" +#include + diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c new file mode 100644 index 000000000..d645ac01c --- /dev/null +++ b/trusty/storage/proxy/proxy.c @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ipc.h" +#include "log.h" +#include "rpmb.h" +#include "storage.h" + +#define REQ_BUFFER_SIZE 4096 +static uint8_t req_buffer[REQ_BUFFER_SIZE + 1]; + +static const char *ss_data_root; +static const char *trusty_devname; +static const char *rpmb_devname; +static const char *ss_srv_name = STORAGE_DISK_PROXY_PORT; + +static const char *_sopts = "hp:d:r:"; +static const struct option _lopts[] = { + {"help", no_argument, NULL, 'h'}, + {"trusty_dev", required_argument, NULL, 'd'}, + {"data_path", required_argument, NULL, 'p'}, + {"rpmb_dev", required_argument, NULL, 'r'}, + {0, 0, 0, 0} +}; + +static void show_usage_and_exit(int code) +{ + ALOGE("usage: storageproxyd -d -p -r \n"); + exit(code); +} + +static int drop_privs(void) +{ + struct __user_cap_header_struct capheader; + struct __user_cap_data_struct capdata[2]; + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { + return -1; + } + + /* + * ensure we're running as the system user + */ + if (setgid(AID_SYSTEM) != 0) { + return -1; + } + + if (setuid(AID_SYSTEM) != 0) { + return -1; + } + + /* + * drop all capabilities except SYS_RAWIO + */ + memset(&capheader, 0, sizeof(capheader)); + memset(&capdata, 0, sizeof(capdata)); + capheader.version = _LINUX_CAPABILITY_VERSION_3; + capheader.pid = 0; + + capdata[CAP_TO_INDEX(CAP_SYS_RAWIO)].permitted = CAP_TO_MASK(CAP_SYS_RAWIO); + capdata[CAP_TO_INDEX(CAP_SYS_RAWIO)].effective = CAP_TO_MASK(CAP_SYS_RAWIO); + + if (capset(&capheader, &capdata[0]) < 0) { + return -1; + } + + /* no-execute for user, no access for group and other */ + umask(S_IXUSR | S_IRWXG | S_IRWXO); + + return 0; +} + +static int handle_req(struct storage_msg *msg, const void *req, size_t req_len) +{ + int rc; + + if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && + (msg->cmd != STORAGE_RPMB_SEND)) { + /* + * handling post commit messages on non rpmb commands are not + * implemented as there is no use case for this yet. + */ + ALOGE("cmd 0x%x: post commit option is not implemented\n", msg->cmd); + msg->result = STORAGE_ERR_UNIMPLEMENTED; + return ipc_respond(msg, NULL, 0); + } + + if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT) { + rc = storage_sync_checkpoint(); + if (rc < 0) { + msg->result = STORAGE_ERR_GENERIC; + return ipc_respond(msg, NULL, 0); + } + } + + switch (msg->cmd) { + case STORAGE_FILE_DELETE: + rc = storage_file_delete(msg, req, req_len); + break; + + case STORAGE_FILE_OPEN: + rc = storage_file_open(msg, req, req_len); + break; + + case STORAGE_FILE_CLOSE: + rc = storage_file_close(msg, req, req_len); + break; + + case STORAGE_FILE_WRITE: + rc = storage_file_write(msg, req, req_len); + break; + + case STORAGE_FILE_READ: + rc = storage_file_read(msg, req, req_len); + break; + + case STORAGE_FILE_GET_SIZE: + rc = storage_file_get_size(msg, req, req_len); + break; + + case STORAGE_FILE_SET_SIZE: + rc = storage_file_set_size(msg, req, req_len); + break; + + case STORAGE_RPMB_SEND: + rc = rpmb_send(msg, req, req_len); + break; + + default: + ALOGE("unhandled command 0x%x\n", msg->cmd); + msg->result = STORAGE_ERR_UNIMPLEMENTED; + rc = 1; + } + + if (rc > 0) { + /* still need to send response */ + rc = ipc_respond(msg, NULL, 0); + } + return rc; +} + +static int proxy_loop(void) +{ + ssize_t rc; + struct storage_msg msg; + + /* enter main message handling loop */ + while (true) { + + /* get incoming message */ + rc = ipc_get_msg(&msg, req_buffer, REQ_BUFFER_SIZE); + if (rc < 0) + return rc; + + /* handle request */ + req_buffer[rc] = 0; /* force zero termination */ + rc = handle_req(&msg, req_buffer, rc); + if (rc) + return rc; + } + + return 0; +} + +static void parse_args(int argc, char *argv[]) +{ + int opt; + int oidx = 0; + + while ((opt = getopt_long(argc, argv, _sopts, _lopts, &oidx)) != -1) { + switch (opt) { + + case 'd': + trusty_devname = strdup(optarg); + break; + + case 'p': + ss_data_root = strdup(optarg); + break; + + case 'r': + rpmb_devname = strdup(optarg); + break; + + default: + ALOGE("unrecognized option (%c):\n", opt); + show_usage_and_exit(EXIT_FAILURE); + } + } + + if (ss_data_root == NULL || + trusty_devname == NULL || + rpmb_devname == NULL) { + ALOGE("missing required argument(s)\n"); + show_usage_and_exit(EXIT_FAILURE); + } + + ALOGI("starting storageproxyd\n"); + ALOGI("storage data root: %s\n", ss_data_root); + ALOGI("trusty dev: %s\n", trusty_devname); + ALOGI("rpmb dev: %s\n", rpmb_devname); +} + +int main(int argc, char *argv[]) +{ + int rc; + uint retry_cnt; + + /* drop privileges */ + if (drop_privs() < 0) + return EXIT_FAILURE; + + /* parse arguments */ + parse_args(argc, argv); + + /* initialize secure storage directory */ + rc = storage_init(ss_data_root); + if (rc < 0) + return EXIT_FAILURE; + + /* open rpmb device */ + rc = rpmb_open(rpmb_devname); + if (rc < 0) + return EXIT_FAILURE; + + /* connect to Trusty secure storage server */ + rc = ipc_connect(trusty_devname, ss_srv_name); + if (rc < 0) + return EXIT_FAILURE; + + /* enter main loop */ + rc = proxy_loop(); + ALOGE("exiting proxy loop with status (%d)\n", rc); + + ipc_disconnect(); + rpmb_close(); + + return (rc < 0) ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/trusty/storage/proxy/rpmb.c b/trusty/storage/proxy/rpmb.c new file mode 100644 index 000000000..91304583a --- /dev/null +++ b/trusty/storage/proxy/rpmb.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipc.h" +#include "log.h" +#include "rpmb.h" +#include "storage.h" + +#define MMC_READ_MULTIPLE_BLOCK 18 +#define MMC_WRITE_MULTIPLE_BLOCK 25 +#define MMC_RELIABLE_WRITE_FLAG (1 << 31) + +#define MMC_RSP_PRESENT (1 << 0) +#define MMC_RSP_CRC (1 << 2) +#define MMC_RSP_OPCODE (1 << 4) +#define MMC_CMD_ADTC (1 << 5) +#define MMC_RSP_SPI_S1 (1 << 7) +#define MMC_RSP_R1 (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE) +#define MMC_RSP_SPI_R1 (MMC_RSP_SPI_S1) + +#define MMC_WRITE_FLAG_R 0 +#define MMC_WRITE_FLAG_W 1 +#define MMC_WRITE_FLAG_RELW (MMC_WRITE_FLAG_W | MMC_RELIABLE_WRITE_FLAG) + +#define MMC_BLOCK_SIZE 512 + +static int rpmb_fd = -1; +static uint8_t read_buf[4096]; + +#ifdef RPMB_DEBUG + +static void print_buf(const char *prefix, const uint8_t *buf, size_t size) +{ + size_t i; + + printf("%s @%p [%zu]", prefix, buf, size); + for (i = 0; i < size; i++) { + if (i && i % 32 == 0) + printf("\n%*s", (int) strlen(prefix), ""); + printf(" %02x", buf[i]); + } + printf("\n"); + fflush(stdout); +} + +#endif + + +int rpmb_send(struct storage_msg *msg, const void *r, size_t req_len) +{ + int rc; + struct { + struct mmc_ioc_multi_cmd multi; + struct mmc_ioc_cmd cmd_buf[3]; + } mmc = {}; + struct mmc_ioc_cmd *cmd = mmc.multi.cmds; + const struct storage_rpmb_send_req *req = r; + + if (req_len < sizeof(*req)) { + ALOGW("malformed rpmb request: invalid length (%zu < %zu)\n", + req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + size_t expected_len = + sizeof(*req) + req->reliable_write_size + req->write_size; + if (req_len != expected_len) { + ALOGW("malformed rpmb request: invalid length (%zu != %zu)\n", + req_len, expected_len); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + const uint8_t *write_buf = req->payload; + if (req->reliable_write_size) { + if ((req->reliable_write_size % MMC_BLOCK_SIZE) != 0) { + ALOGW("invalid reliable write size %u\n", req->reliable_write_size); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + cmd->write_flag = MMC_WRITE_FLAG_RELW; + cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK; + cmd->flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + cmd->blksz = MMC_BLOCK_SIZE; + cmd->blocks = req->reliable_write_size / MMC_BLOCK_SIZE; + mmc_ioc_cmd_set_data((*cmd), write_buf); +#ifdef RPMB_DEBUG + ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag); + print_buf("request: ", write_buf, req->reliable_write_size); +#endif + write_buf += req->reliable_write_size; + mmc.multi.num_of_cmds++; + cmd++; + } + + if (req->write_size) { + if ((req->write_size % MMC_BLOCK_SIZE) != 0) { + ALOGW("invalid write size %u\n", req->write_size); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + cmd->write_flag = MMC_WRITE_FLAG_W; + cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK; + cmd->flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + cmd->blksz = MMC_BLOCK_SIZE; + cmd->blocks = req->write_size / MMC_BLOCK_SIZE; + mmc_ioc_cmd_set_data((*cmd), write_buf); +#ifdef RPMB_DEBUG + ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag); + print_buf("request: ", write_buf, req->write_size); +#endif + write_buf += req->write_size; + mmc.multi.num_of_cmds++; + cmd++; + } + + if (req->read_size) { + if (req->read_size % MMC_BLOCK_SIZE != 0 || + req->read_size > sizeof(read_buf)) { + ALOGE("%s: invalid read size %u\n", __func__, req->read_size); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + cmd->write_flag = MMC_WRITE_FLAG_R; + cmd->opcode = MMC_READ_MULTIPLE_BLOCK; + cmd->flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC, + cmd->blksz = MMC_BLOCK_SIZE; + cmd->blocks = req->read_size / MMC_BLOCK_SIZE; + mmc_ioc_cmd_set_data((*cmd), read_buf); +#ifdef RPMB_DEBUG + ALOGI("opcode: 0x%x, write_flag: 0x%x\n", cmd->opcode, cmd->write_flag); +#endif + mmc.multi.num_of_cmds++; + cmd++; + } + + rc = ioctl(rpmb_fd, MMC_IOC_MULTI_CMD, &mmc.multi); + if (rc < 0) { + ALOGE("%s: mmc ioctl failed: %d, %s\n", __func__, rc, strerror(errno)); + msg->result = STORAGE_ERR_GENERIC; + goto err_response; + } +#ifdef RPMB_DEBUG + if (req->read_size) + print_buf("response: ", read_buf, req->read_size); +#endif + + if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) { + /* + * Nothing todo for post msg commit request as MMC_IOC_MULTI_CMD + * is fully synchronous in this implementation. + */ + } + + msg->result = STORAGE_NO_ERROR; + return ipc_respond(msg, read_buf, req->read_size); + +err_response: + return ipc_respond(msg, NULL, 0); +} + + +int rpmb_open(const char *rpmb_devname) +{ + int rc; + + rc = open(rpmb_devname, O_RDWR, 0); + if (rc < 0) { + ALOGE("unable (%d) to open rpmb device '%s': %s\n", + errno, rpmb_devname, strerror(errno)); + return rc; + } + rpmb_fd = rc; + return 0; +} + +void rpmb_close(void) +{ + close(rpmb_fd); + rpmb_fd = -1; +} + diff --git a/trusty/storage/proxy/rpmb.h b/trusty/storage/proxy/rpmb.h new file mode 100644 index 000000000..85cff44d9 --- /dev/null +++ b/trusty/storage/proxy/rpmb.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +int rpmb_open(const char *rpmb_devname); +int rpmb_send(struct storage_msg *msg, const void *r, size_t req_len); +void rpmb_close(void); diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c new file mode 100644 index 000000000..c61e89d78 --- /dev/null +++ b/trusty/storage/proxy/storage.c @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "ipc.h" +#include "storage.h" + +#define FD_TBL_SIZE 64 +#define MAX_READ_SIZE 4096 + +enum sync_state { + SS_UNUSED = -1, + SS_CLEAN = 0, + SS_DIRTY = 1, +}; + +static int ssdir_fd = -1; +static const char *ssdir_name; + +static enum sync_state fs_state; +static enum sync_state dir_state; +static enum sync_state fd_state[FD_TBL_SIZE]; + +static struct { + struct storage_file_read_resp hdr; + uint8_t data[MAX_READ_SIZE]; +} read_rsp; + +static uint32_t insert_fd(int open_flags, int fd) +{ + uint32_t handle = fd; + + if (open_flags & O_CREAT) { + dir_state = SS_DIRTY; + } + + if (handle < FD_TBL_SIZE) { + fd_state[fd] = SS_CLEAN; /* fd clean */ + if (open_flags & O_TRUNC) { + fd_state[fd] = SS_DIRTY; /* set fd dirty */ + } + } else { + ALOGW("%s: untracked fd %u\n", __func__, fd); + if (open_flags & (O_TRUNC | O_CREAT)) { + fs_state = SS_DIRTY; + } + } + return handle; +} + +static int lookup_fd(uint32_t handle, bool dirty) +{ + if (dirty) { + if (handle < FD_TBL_SIZE) { + fd_state[handle] = SS_DIRTY; + } else { + fs_state = SS_DIRTY; + } + } + return handle; +} + +static int remove_fd(uint32_t handle) +{ + if (handle < FD_TBL_SIZE) { + fd_state[handle] = SS_UNUSED; /* set to uninstalled */ + } + return handle; +} + +static enum storage_err translate_errno(int error) +{ + enum storage_err result; + switch (error) { + case 0: + result = STORAGE_NO_ERROR; + break; + case EBADF: + case EINVAL: + case ENOTDIR: + case EISDIR: + case ENAMETOOLONG: + result = STORAGE_ERR_NOT_VALID; + break; + case ENOENT: + result = STORAGE_ERR_NOT_FOUND; + break; + case EEXIST: + result = STORAGE_ERR_EXIST; + break; + case EPERM: + case EACCES: + result = STORAGE_ERR_ACCESS; + break; + default: + result = STORAGE_ERR_GENERIC; + break; + } + + return result; +} + +static ssize_t write_with_retry(int fd, const void *buf_, size_t size, off_t offset) +{ + ssize_t rc; + const uint8_t *buf = buf_; + + while (size > 0) { + rc = TEMP_FAILURE_RETRY(pwrite(fd, buf, size, offset)); + if (rc < 0) + return rc; + size -= rc; + buf += rc; + offset += rc; + } + return 0; +} + +static ssize_t read_with_retry(int fd, void *buf_, size_t size, off_t offset) +{ + ssize_t rc; + size_t rcnt = 0; + uint8_t *buf = buf_; + + while (size > 0) { + rc = TEMP_FAILURE_RETRY(pread(fd, buf, size, offset)); + if (rc < 0) + return rc; + if (rc == 0) + break; + size -= rc; + buf += rc; + offset += rc; + rcnt += rc; + } + return rcnt; +} + +int storage_file_delete(struct storage_msg *msg, + const void *r, size_t req_len) +{ + char *path = NULL; + const struct storage_file_delete_req *req = r; + + if (req_len < sizeof(*req)) { + ALOGE("%s: invalid request length (%zd < %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + size_t fname_len = strlen(req->name); + if (fname_len != req_len - sizeof(*req)) { + ALOGE("%s: invalid filename length (%zd != %zd)\n", + __func__, fname_len, req_len - sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + int rc = asprintf(&path, "%s/%s", ssdir_name, req->name); + if (rc < 0) { + ALOGE("%s: asprintf failed\n", __func__); + msg->result = STORAGE_ERR_GENERIC; + goto err_response; + } + + dir_state = SS_DIRTY; + rc = unlink(path); + if (rc < 0) { + rc = errno; + if (errno == ENOENT) { + ALOGV("%s: error (%d) unlinking file '%s'\n", + __func__, rc, path); + } else { + ALOGE("%s: error (%d) unlinking file '%s'\n", + __func__, rc, path); + } + msg->result = translate_errno(rc); + goto err_response; + } + + ALOGV("%s: \"%s\"\n", __func__, path); + msg->result = STORAGE_NO_ERROR; + +err_response: + if (path) + free(path); + return ipc_respond(msg, NULL, 0); +} + + +int storage_file_open(struct storage_msg *msg, + const void *r, size_t req_len) +{ + char *path = NULL; + const struct storage_file_open_req *req = r; + struct storage_file_open_resp resp = {0}; + + if (req_len < sizeof(*req)) { + ALOGE("%s: invalid request length (%zd < %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + size_t fname_len = strlen(req->name); + if (fname_len != req_len - sizeof(*req)) { + ALOGE("%s: invalid filename length (%zd != %zd)\n", + __func__, fname_len, req_len - sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + int rc = asprintf(&path, "%s/%s", ssdir_name, req->name); + if (rc < 0) { + ALOGE("%s: asprintf failed\n", __func__); + msg->result = STORAGE_ERR_GENERIC; + goto err_response; + } + + int open_flags = O_RDWR; + + if (req->flags & STORAGE_FILE_OPEN_TRUNCATE) + open_flags |= O_TRUNC; + + if (req->flags & STORAGE_FILE_OPEN_CREATE) { + /* open or create */ + if (req->flags & STORAGE_FILE_OPEN_CREATE_EXCLUSIVE) { + /* create exclusive */ + open_flags |= O_CREAT | O_EXCL; + rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR)); + } else { + /* try open first */ + rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR)); + if (rc == -1 && errno == ENOENT) { + /* then try open with O_CREATE */ + open_flags |= O_CREAT; + rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR)); + } + + } + } else { + /* open an existing file */ + rc = TEMP_FAILURE_RETRY(open(path, open_flags, S_IRUSR | S_IWUSR)); + } + + if (rc < 0) { + rc = errno; + if (errno == EEXIST || errno == ENOENT) { + ALOGV("%s: failed to open file \"%s\": %s\n", + __func__, path, strerror(errno)); + } else { + ALOGE("%s: failed to open file \"%s\": %s\n", + __func__, path, strerror(errno)); + } + msg->result = translate_errno(rc); + goto err_response; + } + free(path); + + /* at this point rc contains storage file fd */ + msg->result = STORAGE_NO_ERROR; + resp.handle = insert_fd(open_flags, rc); + ALOGV("%s: \"%s\": fd = %u: handle = %d\n", + __func__, path, rc, resp.handle); + + return ipc_respond(msg, &resp, sizeof(resp)); + +err_response: + if (path) + free(path); + return ipc_respond(msg, NULL, 0); +} + +int storage_file_close(struct storage_msg *msg, + const void *r, size_t req_len) +{ + const struct storage_file_close_req *req = r; + + if (req_len != sizeof(*req)) { + ALOGE("%s: invalid request length (%zd != %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + int fd = remove_fd(req->handle); + ALOGV("%s: handle = %u: fd = %u\n", __func__, req->handle, fd); + + int rc = fsync(fd); + if (rc < 0) { + rc = errno; + ALOGE("%s: fsync failed for fd=%u: %s\n", + __func__, fd, strerror(errno)); + msg->result = translate_errno(rc); + goto err_response; + } + + rc = close(fd); + if (rc < 0) { + rc = errno; + ALOGE("%s: close failed for fd=%u: %s\n", + __func__, fd, strerror(errno)); + msg->result = translate_errno(rc); + goto err_response; + } + + msg->result = STORAGE_NO_ERROR; + +err_response: + return ipc_respond(msg, NULL, 0); +} + + +int storage_file_write(struct storage_msg *msg, + const void *r, size_t req_len) +{ + int rc; + const struct storage_file_write_req *req = r; + + if (req_len < sizeof(*req)) { + ALOGE("%s: invalid request length (%zd < %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + int fd = lookup_fd(req->handle, true); + if (write_with_retry(fd, &req->data[0], req_len - sizeof(*req), + req->offset) < 0) { + rc = errno; + ALOGW("%s: error writing file (fd=%d): %s\n", + __func__, fd, strerror(errno)); + msg->result = translate_errno(rc); + goto err_response; + } + + msg->result = STORAGE_NO_ERROR; + +err_response: + return ipc_respond(msg, NULL, 0); +} + + +int storage_file_read(struct storage_msg *msg, + const void *r, size_t req_len) +{ + int rc; + const struct storage_file_read_req *req = r; + + if (req_len != sizeof(*req)) { + ALOGE("%s: invalid request length (%zd != %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + if (req->size > MAX_READ_SIZE) { + ALOGW("%s: request is too large (%zd > %zd) - refusing\n", + __func__, req->size, MAX_READ_SIZE); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + int fd = lookup_fd(req->handle, false); + ssize_t read_res = read_with_retry(fd, read_rsp.hdr.data, req->size, + (off_t)req->offset); + if (read_res < 0) { + rc = errno; + ALOGW("%s: error reading file (fd=%d): %s\n", + __func__, fd, strerror(errno)); + msg->result = translate_errno(rc); + goto err_response; + } + + msg->result = STORAGE_NO_ERROR; + return ipc_respond(msg, &read_rsp, read_res + sizeof(read_rsp.hdr)); + +err_response: + return ipc_respond(msg, NULL, 0); +} + + +int storage_file_get_size(struct storage_msg *msg, + const void *r, size_t req_len) +{ + const struct storage_file_get_size_req *req = r; + struct storage_file_get_size_resp resp = {0}; + + if (req_len != sizeof(*req)) { + ALOGE("%s: invalid request length (%zd != %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + struct stat stat; + int fd = lookup_fd(req->handle, false); + int rc = fstat(fd, &stat); + if (rc < 0) { + rc = errno; + ALOGE("%s: error stat'ing file (fd=%d): %s\n", + __func__, fd, strerror(errno)); + msg->result = translate_errno(rc); + goto err_response; + } + + resp.size = stat.st_size; + msg->result = STORAGE_NO_ERROR; + return ipc_respond(msg, &resp, sizeof(resp)); + +err_response: + return ipc_respond(msg, NULL, 0); +} + + +int storage_file_set_size(struct storage_msg *msg, + const void *r, size_t req_len) +{ + const struct storage_file_set_size_req *req = r; + + if (req_len != sizeof(*req)) { + ALOGE("%s: invalid request length (%zd != %zd)\n", + __func__, req_len, sizeof(*req)); + msg->result = STORAGE_ERR_NOT_VALID; + goto err_response; + } + + int fd = lookup_fd(req->handle, true); + int rc = TEMP_FAILURE_RETRY(ftruncate(fd, req->size)); + if (rc < 0) { + rc = errno; + ALOGE("%s: error truncating file (fd=%d): %s\n", + __func__, fd, strerror(errno)); + msg->result = translate_errno(rc); + goto err_response; + } + + msg->result = STORAGE_NO_ERROR; + +err_response: + return ipc_respond(msg, NULL, 0); +} + +int storage_init(const char *dirname) +{ + fs_state = SS_CLEAN; + dir_state = SS_CLEAN; + for (uint i = 0; i < FD_TBL_SIZE; i++) { + fd_state[i] = SS_UNUSED; /* uninstalled */ + } + + ssdir_fd = open(dirname, O_RDONLY); + if (ssdir_fd < 0) { + ALOGE("failed to open ss root dir \"%s\": %s\n", + dirname, strerror(errno)); + return -1; + } + ssdir_name = dirname; + return 0; +} + +int storage_sync_checkpoint(void) +{ + int rc; + + /* sync fd table and reset it to clean state first */ + for (uint fd = 0; fd < FD_TBL_SIZE; fd++) { + if (fd_state[fd] == SS_DIRTY) { + if (fs_state == SS_CLEAN) { + /* need to sync individual fd */ + rc = fsync(fd); + if (rc < 0) { + ALOGE("fsync for fd=%d failed: %s\n", fd, strerror(errno)); + return rc; + } + } + fd_state[fd] = SS_CLEAN; /* set to clean */ + } + } + + /* check if we need to sync the directory */ + if (dir_state == SS_DIRTY) { + if (fs_state == SS_CLEAN) { + rc = fsync(ssdir_fd); + if (rc < 0) { + ALOGE("fsync for ssdir failed: %s\n", strerror(errno)); + return rc; + } + } + dir_state = SS_CLEAN; /* set to clean */ + } + + /* check if we need to sync the whole fs */ + if (fs_state == SS_DIRTY) { + rc = syscall(SYS_syncfs, ssdir_fd); + if (rc < 0) { + ALOGE("syncfs failed: %s\n", strerror(errno)); + return rc; + } + fs_state = SS_CLEAN; + } + + return 0; +} + diff --git a/trusty/storage/proxy/storage.h b/trusty/storage/proxy/storage.h new file mode 100644 index 000000000..5a670d4b7 --- /dev/null +++ b/trusty/storage/proxy/storage.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +int storage_file_delete(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_file_open(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_file_close(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_file_write(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_file_read(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_file_get_size(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_file_set_size(struct storage_msg *msg, + const void *req, size_t req_len); + +int storage_init(const char *dirname); + +int storage_sync_checkpoint(void); + diff --git a/trusty/trusty-storage.mk b/trusty/trusty-storage.mk new file mode 100644 index 000000000..3f263167c --- /dev/null +++ b/trusty/trusty-storage.mk @@ -0,0 +1,18 @@ +# +# Copyright (C) 2015 The Android Open-Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +PRODUCT_PACKAGES += \ + storageproxyd \