/* * Copyright (C) 2018 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 "rpmb_dev.h" #include #include #include #include #include #include #include #include #include #include /* * Receives data until one of the following is true: * - The buffer is full (return will be len) * - The connection closed (return > 0, < len) * - An error occurred (return will be the negative error code from recv) */ ssize_t recv_until(int sock, void* dest, size_t len) { size_t bytes_recvd = 0; while (bytes_recvd < len) { ssize_t ret = recv(sock, dest, len - bytes_recvd, 0); if (ret < 0) { return ret; } dest += ret; bytes_recvd += ret; if (ret == 0) { break; } } return bytes_recvd; } /* * Handles an incoming connection to the rpmb daemon. * Returns 0 if the client disconnects without violating the protocol. * Returns a negative value if we terminated the connection abnormally. * * Arguments: * conn_sock - an fd to send/recv on * s - an initialized rpmb device */ int handle_conn(struct rpmb_dev_state* s, int conn_sock) { int ret; while (true) { memset(s->res, 0, sizeof(s->res)); ret = recv_until(conn_sock, &s->res_count, sizeof(s->res_count)); /* * Disconnected while not in the middle of anything. */ if (ret <= 0) { return 0; } if (s->res_count > MAX_PACKET_COUNT) { fprintf(stderr, "rpmb_dev: Receive count too large: %d\n", s->res_count); return -1; } if (s->res_count <= 0) { fprintf(stderr, "rpmb_dev: Receive count too small: %d\n", s->res_count); return -1; } ret = recv_until(conn_sock, &s->cmd_count, sizeof(s->cmd_count)); if (ret != sizeof(s->cmd_count)) { fprintf(stderr, "rpmb_dev: Failed to read cmd_count"); return -1; } if (s->cmd_count == 0) { fprintf(stderr, "rpmb_dev: Must contain at least one command\n"); return -1; } if (s->cmd_count > MAX_PACKET_COUNT) { fprintf(stderr, "rpmb_dev: Command count is too large\n"); return -1; } size_t cmd_size = s->cmd_count * sizeof(s->cmd[0]); ret = recv_until(conn_sock, s->cmd, cmd_size); if (ret != (int)cmd_size) { fprintf(stderr, "rpmb_dev: Failed to read command: " "cmd_size: %zu ret: %d, %s\n", cmd_size, ret, strerror(errno)); return -1; } rpmb_dev_process_cmd(s); size_t resp_size = sizeof(s->res[0]) * s->res_count; ret = send(conn_sock, s->res, resp_size, 0); if (ret != (int)resp_size) { fprintf(stderr, "rpmb_dev: Failed to send response: %d, %s\n", ret, strerror(errno)); return -1; } } } void usage(const char* argv0) { fprintf(stderr, "Usage: %s [-d|--dev] [--sock] \n", argv0); fprintf(stderr, "or: %s [-d|--dev] [--size ] [--key key]\n", argv0); } int main(int argc, char** argv) { struct rpmb_dev_state s = {0}; int ret; int cmdres_sock; struct sockaddr_un cmdres_sockaddr; const char* data_file_name = NULL; const char* socket_path = NULL; int open_flags; int init = false; struct option long_options[] = {{"size", required_argument, 0, 0}, {"key", required_argument, 0, 0}, {"sock", required_argument, 0, 0}, {"dev", required_argument, 0, 'd'}, {"init", no_argument, &init, true}, {"verbose", no_argument, &verbose, true}, {0, 0, 0, 0}}; memset(&s.header, 0, sizeof(s.header)); while (1) { int c; int option_index = 0; c = getopt_long(argc, argv, "d:", long_options, &option_index); if (c == -1) { break; } switch (c) { /* long args */ case 0: switch (option_index) { /* size */ case 0: s.header.max_block = atoi(optarg) - 1; break; /* key */ case 1: for (size_t i = 0; i < sizeof(s.header.key.byte); i++) { if (!optarg) { break; } s.header.key.byte[i] = strtol(optarg, &optarg, 16); s.header.key_programmed = 1; } break; /* sock */ case 2: socket_path = optarg; break; } break; /* dev */ case 'd': data_file_name = optarg; break; default: usage(argv[0]); return EXIT_FAILURE; } } /* * We always need a data file, and at exactly one of --init or --sock * must be specified. */ if (!data_file_name || (!init == !socket_path)) { usage(argv[0]); return EXIT_FAILURE; } open_flags = O_RDWR; if (init) { open_flags |= O_CREAT | O_TRUNC; } s.data_fd = open(data_file_name, open_flags, S_IWUSR | S_IRUSR); if (s.data_fd < 0) { fprintf(stderr, "rpmb_dev: Failed to open rpmb data file, %s: %s\n", data_file_name, strerror(errno)); return EXIT_FAILURE; } if (init) { /* Create new rpmb data file */ if (s.header.max_block == 0) { s.header.max_block = 512 - 1; } ret = write(s.data_fd, &s.header, sizeof(s.header)); if (ret != sizeof(s.header)) { fprintf(stderr, "rpmb_dev: Failed to write rpmb data file: %d, %s\n", ret, strerror(errno)); return EXIT_FAILURE; } return EXIT_SUCCESS; } ret = read(s.data_fd, &s.header, sizeof(s.header)); if (ret != sizeof(s.header)) { fprintf(stderr, "rpmb_dev: Failed to read rpmb data file: %d, %s\n", ret, strerror(errno)); return EXIT_FAILURE; } cmdres_sock = socket(AF_UNIX, SOCK_STREAM, 0); if (cmdres_sock < 0) { fprintf(stderr, "rpmb_dev: Failed to create command/response socket: %s\n", strerror(errno)); return EXIT_FAILURE; } cmdres_sockaddr.sun_family = AF_UNIX; strncpy(cmdres_sockaddr.sun_path, socket_path, sizeof(cmdres_sockaddr.sun_path)); ret = bind(cmdres_sock, (struct sockaddr*)&cmdres_sockaddr, sizeof(struct sockaddr_un)); if (ret < 0) { fprintf(stderr, "rpmb_dev: Failed to bind command/response socket: %s: %s\n", socket_path, strerror(errno)); return EXIT_FAILURE; } ret = listen(cmdres_sock, 1); if (ret < 0) { fprintf(stderr, "rpmb_dev: Failed to listen on command/response socket: %s\n", strerror(errno)); return EXIT_FAILURE; } while (true) { int conn_sock = accept(cmdres_sock, NULL, NULL); if (conn_sock < 0) { fprintf(stderr, "rpmb_dev: Could not accept connection: %s\n", strerror(errno)); return EXIT_FAILURE; } ret = handle_conn(&s, conn_sock); close(conn_sock); if (ret) { fprintf(stderr, "rpmb_dev: Connection terminated: %d", ret); } } }