/* * Copyright (C) 2024 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 "aidl_service.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "block_device_tipc.h" #include "client.h" #include "client_session.h" #include "file.h" #include "storage_limits.h" using ::android::RpcServerTrusty; using ::android::RpcSession; using ::android::sp; using ::android::wp; using ::android::binder::Status; using ::android::hardware::security::see::storage::Availability; using ::android::hardware::security::see::storage::BnDir; using ::android::hardware::security::see::storage::BnFile; using ::android::hardware::security::see::storage::BnSecureStorage; using ::android::hardware::security::see::storage::BnStorageSession; using ::android::hardware::security::see::storage::CreationMode; using ::android::hardware::security::see::storage::FileMode; using ::android::hardware::security::see::storage::Filesystem; using ::android::hardware::security::see::storage::IDir; using ::android::hardware::security::see::storage::IFile; using ::android::hardware::security::see::storage::Integrity; using ::android::hardware::security::see::storage::ISecureStorage; using ::android::hardware::security::see::storage::IStorageSession; using ::android::hardware::security::see::storage::OpenOptions; #define SS_ERR(args...) fprintf(stderr, "ss-aidl: " args) namespace storage_service { namespace { constexpr uint32_t kAclFlags = #if TEST_BUILD IPC_PORT_ALLOW_TA_CONNECT | IPC_PORT_ALLOW_NS_CONNECT; #else IPC_PORT_ALLOW_TA_CONNECT; #endif constexpr size_t kMaxBufferSize = STORAGE_MAX_BUFFER_SIZE; static Status status_from_storage_err(storage_err err) { switch (err) { case storage_err::STORAGE_NO_ERROR: return Status::ok(); case storage_err::STORAGE_ERR_GENERIC: return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); case storage_err::STORAGE_ERR_NOT_VALID: return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT); case storage_err::STORAGE_ERR_UNIMPLEMENTED: return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION); case storage_err::STORAGE_ERR_ACCESS: return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); case storage_err::STORAGE_ERR_NOT_FOUND: return Status::fromServiceSpecificError(ISecureStorage::ERR_NOT_FOUND); case storage_err::STORAGE_ERR_EXIST: return Status::fromServiceSpecificError( ISecureStorage::ERR_ALREADY_EXISTS); case storage_err::STORAGE_ERR_TRANSACT: return Status::fromServiceSpecificError( ISecureStorage::ERR_BAD_TRANSACTION); case storage_err::STORAGE_ERR_NOT_ALLOWED: return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); case storage_err::STORAGE_ERR_CORRUPTED: return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE); case storage_err::STORAGE_ERR_FS_REPAIRED: // TODO: Distinguish rolled back vs reset; catch other tampering return Status::fromServiceSpecificError( ISecureStorage::ERR_FS_TAMPERED); default: return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION, "Unknown error code."); } } static file_create_mode create_mode(CreationMode mode) { switch (mode) { case CreationMode::CREATE_EXCLUSIVE: return file_create_mode::FILE_OPEN_CREATE_EXCLUSIVE; case CreationMode::CREATE: return file_create_mode::FILE_OPEN_CREATE; case CreationMode::NO_CREATE: return file_create_mode::FILE_OPEN_NO_CREATE; } } static Status get_fs(const Filesystem& filesystem, storage_filesystem_type* out) { switch (filesystem.integrity) { case Integrity::TAMPER_PROOF_AT_REST: { // TP is persistent and available before userdata *out = STORAGE_TP; break; } case Integrity::TAMPER_DETECT: { switch (filesystem.availability) { case Availability::BEFORE_USERDATA: { if (filesystem.persistent) { return Status::fromExceptionCode( Status::EX_UNSUPPORTED_OPERATION, "Unsupported Filesystem properties: TDEA does not guarantee persistence"); } *out = STORAGE_TDEA; break; } case Availability::AFTER_USERDATA: { *out = filesystem.persistent ? STORAGE_TDP : STORAGE_TD; break; } default: return Status::fromExceptionCode( Status::EX_UNSUPPORTED_OPERATION, "Unsupported Filesystem properties: Unknown Availability value"); } break; } default: return Status::fromExceptionCode( Status::EX_UNSUPPORTED_OPERATION, "Unsupported Filesystem properties: Unknown Integrity value"); } return Status::ok(); } class StorageClientSession { public: StorageClientSession(struct fs* fs, bool* fs_active, const uuid_t* peer) : inner_(), active_(fs_active) { storage_client_session_init(&inner_, fs, peer); } ~StorageClientSession() { storage_client_session_destroy(&inner_); } storage_client_session* get() { return *active_ ? &inner_ : nullptr; } private: storage_client_session inner_; bool* active_; }; class Dir : public BnDir { public: Dir(std::weak_ptr session) : session_(std::move(session)), last_state_(storage_file_list_flag::STORAGE_FILE_LIST_START), last_name_() {} Status readNextFilenames(int32_t max_count, std::vector* out) final { constexpr size_t kMaxFilenames = STORAGE_MAX_BUFFER_SIZE / FS_PATH_MAX; if (max_count < 0) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "maxCount must not be negative."); } size_t max_names = (max_count == 0) ? kMaxFilenames : std::min(kMaxFilenames, static_cast(max_count)); std::shared_ptr session = session_.lock(); if (session == nullptr) { return Status::fromExceptionCode( Status::EX_ILLEGAL_STATE, "IDir cannot be used after its parent session has been destroyed."); } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } if (last_state_ == storage_file_list_flag::STORAGE_FILE_LIST_END) { return Status::ok(); } ListCallbackData data = { .out = out, .curr_flags = last_state_, }; storage_err result = storage_file_list( client_session, max_names, last_state_, last_name_.data(), last_name_.size(), op_flags(), [](void* callback_data, size_t max_path_len) { return true; }, [](void* callback_data, enum storage_file_list_flag flags, const char* path, size_t path_len) { auto& data = *static_cast(callback_data); data.curr_flags = flags; if (flags == storage_file_list_flag::STORAGE_FILE_LIST_END) { return; } data.out->emplace_back(path, path_len); // TODO: Do we need to tell the caller whether the file is // committed, added, removed? }, &data); last_state_ = data.curr_flags; last_name_ = out->empty() ? "" : out->back(); return status_from_storage_err(result); } private: struct ListCallbackData { std::vector* out; enum storage_file_list_flag curr_flags; }; storage_op_flags op_flags() { return storage_op_flags{ .allow_repaired = false, .complete_transaction = false, .update_checkpoint = false, }; } std::weak_ptr session_; enum storage_file_list_flag last_state_; std::string last_name_; }; class File : public BnFile { public: File(std::weak_ptr session, uint32_t file_handle, FileMode access_mode) : session_(std::move(session)), file_handle_(file_handle), access_mode_(access_mode) {} ~File() { std::shared_ptr session = session_.lock(); if (session == nullptr) { return; } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return; } (void)storage_file_close(client_session, file_handle_, op_flags()); } Status read(int64_t size, int64_t offset, std::vector* out) final { if (access_mode_ != FileMode::READ_ONLY && access_mode_ != FileMode::READ_WRITE) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "File not opened for reading."); } if (size < 0) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Size must not be negative."); } if (size > std::numeric_limits::max()) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Size would overflow"); } if (offset < 0) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Offset must not be negative."); } std::shared_ptr session = session_.lock(); if (session == nullptr) { return Status::fromExceptionCode( Status::EX_ILLEGAL_STATE, "IFile cannot be used after its parent session has been destroyed."); } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } out->resize( std::min(size, static_cast(STORAGE_MAX_BUFFER_SIZE))); size_t out_len = out->size(); storage_err result = storage_file_read(client_session, file_handle_, size, offset, op_flags(), out->data(), &out_len); out->resize(out_len); return status_from_storage_err(result); } Status write(int64_t offset, const std::vector& buffer, int64_t* out) final { if (access_mode_ != FileMode::WRITE_ONLY && access_mode_ != FileMode::READ_WRITE) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "File not opened for writing."); } if (offset < 0) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Offset must not be negative."); } std::shared_ptr session = session_.lock(); if (session == nullptr) { return Status::fromExceptionCode( Status::EX_ILLEGAL_STATE, "IFile cannot be used after its parent session has been destroyed."); } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } storage_err result = storage_file_write(session->get(), file_handle_, offset, buffer.data(), buffer.size(), op_flags()); if (result != storage_err::STORAGE_NO_ERROR) { return status_from_storage_err(result); } *out = buffer.size(); return Status::ok(); } Status getSize(int64_t* out) final { std::shared_ptr session = session_.lock(); if (session == nullptr) { return Status::fromExceptionCode( Status::EX_ILLEGAL_STATE, "IFile cannot be used after its parent session has been destroyed."); } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } uint64_t size; storage_err result = storage_file_get_size(session->get(), file_handle_, op_flags(), &size); if (result != storage_err::STORAGE_NO_ERROR) { return status_from_storage_err(result); } if (size > std::numeric_limits::max()) { return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Size would overflow"); } *out = static_cast(size); return Status::ok(); } Status setSize(int64_t new_size) final { if (access_mode_ != FileMode::WRITE_ONLY && access_mode_ != FileMode::READ_WRITE) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "File not opened for writing."); } std::shared_ptr session = session_.lock(); if (session == nullptr) { return Status::fromExceptionCode( Status::EX_ILLEGAL_STATE, "IFile cannot be used after its parent session has been destroyed."); } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } storage_err result = storage_file_set_size(session->get(), file_handle_, new_size, op_flags()); return status_from_storage_err(result); } Status rename(const std::string& new_name, CreationMode dest_create_mode) { if (access_mode_ != FileMode::WRITE_ONLY && access_mode_ != FileMode::READ_WRITE) { return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "File not opened for writing."); } std::shared_ptr session = session_.lock(); if (session == nullptr) { return Status::fromExceptionCode( Status::EX_ILLEGAL_STATE, "IFile cannot be used after its parent session has been destroyed."); } storage_client_session* client_session = session->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } storage_err result = storage_file_move( session->get(), file_handle_, true, nullptr, 0, new_name.data(), new_name.size(), create_mode(dest_create_mode), op_flags()); return status_from_storage_err(result); } private: storage_op_flags op_flags() { return storage_op_flags{ .allow_repaired = false, .complete_transaction = false, .update_checkpoint = false, }; } std::weak_ptr session_; uint32_t file_handle_; FileMode access_mode_; }; class StorageSession : public BnStorageSession { public: StorageSession(std::shared_ptr session) : session_(std::move(session)) {} Status stageChangesForCommitOnAbUpdateComplete() final { return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION); } Status commitChanges() final { return endTransactions(true); } Status abandonChanges() final { return endTransactions(false); } Status openFile(const std::string& file_name, const OpenOptions& options, sp* out) final { storage_client_session* client_session = session_->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } uint32_t file_handle; storage_err err = storage_file_open( client_session, file_name.data(), file_name.size(), create_mode(options.createMode), options.truncateOnOpen, storage_op_flags{ .allow_repaired = false, .complete_transaction = false, .update_checkpoint = false, }, &file_handle); if (err != storage_err::STORAGE_NO_ERROR) { return status_from_storage_err(err); } *out = sp::make(session_, file_handle, options.accessMode); return Status::ok(); } Status deleteFile(const std::string& file_name) final { storage_client_session* client_session = session_->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } storage_err err = storage_file_delete( client_session, file_name.data(), file_name.size(), storage_op_flags{ .allow_repaired = false, .complete_transaction = false, .update_checkpoint = false, }); return status_from_storage_err(err); } Status renameFile(const std::string& file_name, const std::string& new_name, CreationMode dest_create_mode) final { storage_client_session* client_session = session_->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } storage_err err = storage_file_move( client_session, 0, false, file_name.data(), file_name.size(), new_name.data(), new_name.size(), create_mode(dest_create_mode), storage_op_flags{ .allow_repaired = false, .complete_transaction = false, .update_checkpoint = false, }); return status_from_storage_err(err); } Status openDir(const std::string& file_name, sp* out) final { if (!file_name.empty()) { return Status::fromExceptionCode( Status::EX_ILLEGAL_ARGUMENT, "Service currently only supports opening the root dir."); } if (session_->get() == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } // TODO: Catch tampering? *out = sp::make(session_); return Status::ok(); } private: Status endTransactions(bool commit_changes) { storage_op_flags flags = { .allow_repaired = false, .complete_transaction = commit_changes, // TODO: Allow updating checkpoint .update_checkpoint = false, }; storage_client_session* client_session = session_->get(); if (client_session == nullptr) { return Status::fromStatusT(android::WOULD_BLOCK); } storage_err result = storage_transaction_end(client_session, flags); return status_from_storage_err(result); } std::shared_ptr session_; }; class StorageService { public: Status MakeSession(const Filesystem& filesystem, const uuid_t* peer, std::shared_ptr* out) { storage_filesystem_type fs_type; Status result = get_fs(filesystem, &fs_type); if (!result.isOk()) { return result; } if (!filesystems_active_[fs_type]) { return Status::fromStatusT(android::WOULD_BLOCK); } *out = std::make_shared( filesystems_[fs_type], &filesystems_active_[fs_type], peer); return Status::ok(); } void DeactivateFilesystem(storage_filesystem_type fs_type) { if (!filesystems_active_[fs_type]) { // The filesystem might be still be inactive because it wasn't // connected when storage_aidl_enable was called, like NS-backed // filesystems would be before NS is available. return; } filesystems_active_[fs_type] = false; } void TryActivateFilesystem(struct block_device_tipc* block_devices, storage_filesystem_type fs_type) { assert(!filesystems_active_[fs_type]); if (!block_device_tipc_fs_connected(block_devices, fs_type)) { return; } if (filesystems_[fs_type] == nullptr) { filesystems_[fs_type] = block_device_tipc_get_fs(block_devices, fs_type); } else { assert(filesystems_[fs_type] == block_device_tipc_get_fs(block_devices, fs_type)); } filesystems_active_[fs_type] = true; } private: std::array filesystems_; std::array filesystems_active_; }; class SecureStorage : public BnSecureStorage { public: SecureStorage(StorageService* service, uuid_t peer) : service_(service), peer_(peer) {} Status startSession(const Filesystem& filesystem, sp* out) final { std::shared_ptr session; Status result = service_->MakeSession(filesystem, &peer_, &session); if (!result.isOk()) { return result; } *out = sp::make(std::move(session)); return Status::ok(); } private: StorageService* service_; uuid_t peer_; }; } // namespace } // namespace storage_service struct storage_service_aidl_context_inner { sp aidl_srv; storage_service::StorageService service; }; int storage_aidl_create_service(struct storage_service_aidl_context* ctx, struct tipc_hset* hset) { auto result = std::make_unique(); auto& service = result->service; auto port_acl = RpcServerTrusty::PortAcl{.flags = storage_service::kAclFlags}; auto aidl_srv = RpcServerTrusty::make( hset, STORAGE_ISECURE_STORAGE_PORT, std::make_shared(port_acl), storage_service::kMaxBufferSize); if (aidl_srv == nullptr) { return EXIT_FAILURE; } aidl_srv->setPerSessionRootObject([&service](wp session, const void* peer, size_t peer_size) -> sp { if (peer_size != sizeof(uuid_t)) { SS_ERR("Creating binder root object, but peer id had unexpected size %zu (expected %zu)", peer_size, sizeof(uuid_t)); return nullptr; } uuid_t peer_uuid = *static_cast(peer); return sp::make(&service, std::move(peer_uuid)); }); result->aidl_srv = std::move(aidl_srv); // Caller now owns underlying storage_service_aidl_context ctx->inner = result.release(); return EXIT_SUCCESS; } void storage_aidl_destroy_service(struct storage_service_aidl_context* ctx) { delete ctx->inner; } void storage_aidl_enable(struct storage_service_aidl_context* self, struct block_device_tipc* block_devices) { storage_service::StorageService& service = self->inner->service; service.TryActivateFilesystem(block_devices, STORAGE_TP); service.TryActivateFilesystem(block_devices, STORAGE_TDEA); service.TryActivateFilesystem(block_devices, STORAGE_TD); service.TryActivateFilesystem(block_devices, STORAGE_TDP); service.TryActivateFilesystem(block_devices, STORAGE_NSP); } void storage_aidl_disable(struct storage_service_aidl_context* self) { storage_service::StorageService& service = self->inner->service; service.DeactivateFilesystem(STORAGE_NSP); service.DeactivateFilesystem(STORAGE_TDP); service.DeactivateFilesystem(STORAGE_TD); service.DeactivateFilesystem(STORAGE_TDEA); service.DeactivateFilesystem(STORAGE_TP); }