Implement working prodinfo blanking.

This commit is contained in:
Michael Scire 2019-05-23 03:10:32 -07:00
parent 3bba035b84
commit 967f14fc7e
13 changed files with 239 additions and 82 deletions

View file

@ -282,7 +282,11 @@ uint32_t configitem_get(bool privileged, ConfigItem item, uint64_t *p_outvalue)
break;
case CONFIGITEM_HAS_RCM_BUG_PATCH:
/* UNOFFICIAL: Gets whether this unit has the RCM bug patched. */
*p_outvalue = (int)(fuse_has_rcm_bug_patch());;
*p_outvalue = (int)(fuse_has_rcm_bug_patch());
break;
case CONFIGITEM_SHOULD_BLANK_PROD_INFO:
/* UNOFFICIAL: Gets whether we should blank out certain parts of PRODINFO. */
*p_outvalue = (int)(exosphere_should_blank_prod_info() != 0);
break;
default:
result = 2;

View file

@ -45,6 +45,7 @@ typedef enum {
CONFIGITEM_NEEDS_SHUTDOWN = 65002,
CONFIGITEM_EXOSPHERE_VERHASH = 65003,
CONFIGITEM_HAS_RCM_BUG_PATCH = 65004,
CONFIGITEM_SHOULD_BLANK_PROD_INFO = 65005,
} ConfigItem;
#define REBOOT_KIND_NO_REBOOT 0

View file

@ -83,3 +83,11 @@ unsigned int exosphere_should_disable_usermode_exception_handlers(void) {
return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS);
}
unsigned int exosphere_should_blank_prod_info(void) {
if (!g_has_loaded_config) {
generic_panic();
}
return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO);
}

View file

@ -39,6 +39,7 @@
#define EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV (1 << 1u)
#define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u)
#define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u)
#define EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO (1 << 4u)
#define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV)
typedef struct {
@ -54,6 +55,7 @@ unsigned int exosphere_should_perform_620_keygen(void);
unsigned int exosphere_should_override_debugmode_priv(void);
unsigned int exosphere_should_override_debugmode_user(void);
unsigned int exosphere_should_disable_usermode_exception_handlers(void);
unsigned int exosphere_should_blank_prod_info(void);
static inline unsigned int exosphere_get_target_firmware_for_init(void) {
const unsigned int magic = MAILBOX_EXOSPHERE_CONFIG_PHYS.magic;

View file

@ -28,6 +28,7 @@
#define EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV (1 << 1u)
#define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u)
#define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u)
#define EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO (1 << 4u)
#define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV)
typedef struct {

View file

@ -199,6 +199,14 @@ static void nxboot_configure_exosphere(uint32_t target_firmware, unsigned int ke
fatal_error("[NXBOOT]: Failed to parse BCT.ini!\n");
}
{
FILE *flag = fopen("atmosphere/flags/blank_prodinfo.flag", "rb");
if (flag != NULL) {
exo_cfg.flags |= EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO;
fclose(flag);
}
}
if ((exo_cfg.target_firmware < ATMOSPHERE_TARGET_FIRMWARE_MIN) || (exo_cfg.target_firmware > ATMOSPHERE_TARGET_FIRMWARE_MAX)) {
fatal_error("[NXBOOT]: Invalid Exosphere target firmware!\n");
}

View file

@ -112,7 +112,6 @@ class IROStorage : public IStorage {
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
};
class ProxyStorage : public IStorage {
private:
FsStorage *base_storage;
@ -172,4 +171,4 @@ class ROProxyStorage : public IROStorage {
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
return fsStorageOperateRange(this->base_storage, operation_type, offset, size, out_range_info);
};
};
};

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "fs_shim.h"
#include "../debug.hpp"
#include "fs_istorage.hpp"
class MemoryStorage : public IStorage {
private:
u8 * const buffer;
const u64 size;
public:
MemoryStorage(void *b, u64 sz) : buffer(static_cast<u8 *>(b)), size(sz) {
/* ... */
}
virtual ~MemoryStorage() {
/* ... */
}
public:
virtual Result Read(void *buffer, size_t size, u64 offset) override {
if (size == 0) {
return ResultSuccess;
}
if (buffer == nullptr) {
return ResultFsNullptrArgument;
}
if (!IStorage::IsRangeValid(offset, size, this->size)) {
return ResultFsOutOfRange;
}
std::memcpy(buffer, this->buffer + offset, size);
return ResultSuccess;
}
virtual Result Write(void *buffer, size_t size, u64 offset) override {
if (size == 0) {
return ResultSuccess;
}
if (buffer == nullptr) {
return ResultFsNullptrArgument;
}
if (!IStorage::IsRangeValid(offset, size, this->size)) {
return ResultFsOutOfRange;
}
std::memcpy(this->buffer + offset, buffer, size);
return ResultSuccess;
}
virtual Result Flush() override {
return ResultSuccess;
}
virtual Result GetSize(u64 *out_size) override {
*out_size = this->size;
return ResultSuccess;
}
virtual Result SetSize(u64 size) override {
return ResultFsUnsupportedOperation;
}
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
switch (operation_type) {
case 2: /* TODO: OperationType_Invalidate */
return ResultSuccess;
case 3: /* TODO: OperationType_Query */
if (out_range_info == nullptr) {
return ResultFsNullptrArgument;
}
/* N checks for size == sizeof(*out_range_info) here, but that's because their wrapper api is bad. */
std::memset(out_range_info, 0, sizeof(*out_range_info));
return ResultSuccess;
default:
return ResultFsUnsupportedOperation;
}
}
};
class ReadOnlyMemoryStorage : public MemoryStorage {
public:
ReadOnlyMemoryStorage(const void *b, u64 sz) : MemoryStorage(const_cast<void *>(b), sz) {
/* ... */
}
virtual ~ReadOnlyMemoryStorage() {
/* ... */
}
public:
virtual Result Write(void *buffer, size_t size, u64 offset) override {
return ResultFsUnsupportedOperation;
}
};

View file

@ -27,12 +27,14 @@
#include "fsmitm_boot0storage.hpp"
#include "fsmitm_romstorage.hpp"
#include "fsmitm_layeredrom.hpp"
#include "fsmitm_utils.hpp"
#include "fs_dir_utils.hpp"
#include "fs_save_utils.hpp"
#include "fs_subdirectory_filesystem.hpp"
#include "fs_directory_savedata_filesystem.hpp"
#include "fs_file_storage.hpp"
#include "fs_memory_storage.hpp"
#include "../debug.hpp"
@ -262,23 +264,13 @@ Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out
const bool is_sysmodule = TitleIdIsSystem(this->title_id);
const bool has_bis_write_flag = Utils::HasFlag(this->title_id, "bis_write");
const bool has_cal0_read_flag = Utils::HasFlag(this->title_id, "cal_read");
const bool has_blank_cal0_flag = Utils::HasGlobalFlag("blank_prodinfo");
const bool has_blank_cal0_flag = ShouldBlankProdInfo();
if (bis_partition_id == BisStorageId_Boot0) {
storage = std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->title_id));
} else if (bis_partition_id == BisStorageId_Prodinfo) {
/* PRODINFO should *never* be writable. */
if (has_blank_cal0_flag) {
FsFile file;
if (R_FAILED((rc = Utils::OpenBlankProdInfoFile(&file)))) {
return rc;
}
storage = std::make_shared<IStorageInterface>(new FileStorage(new ProxyFile(&file)));
if (out_storage.IsDomain()) {
out_domain_id = file.s.object_id;
}
return rc;
storage = std::make_shared<IStorageInterface>(new MitmProxyStorage(std::make_unique<ReadOnlyMemoryStorage>(Utils::GetBlankProdInfoBuffer(), ProdInfoSize), bis_storage.s));
} else if (is_sysmodule || has_cal0_read_flag) {
storage = std::make_shared<IStorageInterface>(new ROProxyStorage(bis_storage));
} else {

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "fs_istorage.hpp"
class MitmProxyStorage : public IStorage {
private:
Service srv_holder;
std::unique_ptr<IStorage> fwd_storage;
public:
MitmProxyStorage(std::unique_ptr<IStorage> st, Service sr = {}) : srv_holder(sr), fwd_storage(std::move(st)) {
/* ... */
}
virtual ~MitmProxyStorage() {
if (serviceIsActive(&srv_holder)) {
serviceClose(&srv_holder);
}
}
public:
virtual Result Read(void *buffer, size_t size, u64 offset) override {
return this->fwd_storage->Read(buffer, size, offset);
}
virtual Result Write(void *buffer, size_t size, u64 offset) override {
return this->fwd_storage->Write(buffer, size, offset);
}
virtual Result Flush() override {
return this->fwd_storage->Flush();
}
virtual Result GetSize(u64 *out_size) override {
return this->fwd_storage->GetSize(out_size);
}
virtual Result SetSize(u64 size) override {
return this->fwd_storage->SetSize(size);
}
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
return this->fwd_storage->OperateRange(operation_type, offset, size, out_range_info);
}
};

View file

@ -60,13 +60,13 @@ static HblOverrideConfig g_hbl_override_config = {
static char g_config_ini_data[0x800];
/* Backup file for CAL0 partition. */
static constexpr size_t ProdInfoSize = 0x8000;
static constexpr const char *BlankProdInfoPath = "/atmosphere/automatic_backups/BLANK_PRODINFO.bin";
static FsFile g_cal0_file = {0};
static u8 g_cal0_storage_backup[ProdInfoSize];
static u8 g_cal0_backup[ProdInfoSize];
static FsFile g_blank_prodinfo_file = {0};
static HosMutex g_blank_cal0_lock;
static bool g_initialized_blank_cal0 = false;
static u8 g_blank_cal0[ProdInfoSize];
static bool IsHexadecimal(const char *str) {
while (*str) {
@ -142,9 +142,6 @@ void Utils::InitializeThreadFunc(void *args) {
fsFileFlush(&g_cal0_file);
}
/* Use one of the storages to make a blank prodinfo file. */
Utils::CreateBlankProdInfo();
/* NOTE: g_cal0_file is intentionally not closed here. This prevents any other process from opening it. */
std::memset(g_cal0_storage_backup, 0, sizeof(g_cal0_storage_backup));
std::memset(g_cal0_backup, 0, sizeof(g_cal0_backup));
@ -672,81 +669,60 @@ void Utils::RebootToFatalError(AtmosphereFatalErrorContext *ctx) {
BpcRebootManager::RebootForFatalError(ctx);
}
void Utils::CreateBlankProdInfo() {
Result rc;
void Utils::EnsureBlankProdInfo() {
std::scoped_lock<HosMutex> lk(g_blank_cal0_lock);
if (g_initialized_blank_cal0) {
return;
}
u8 *cal0;
if (IsCal0Valid(g_cal0_backup)) {
cal0 = g_cal0_backup;
} else if (IsCal0Valid(g_cal0_storage_backup)) {
cal0 = g_cal0_storage_backup;
} else {
/* Read CAL0 in from NAND. */
{
FsStorage cal0_storage;
if (R_FAILED(fsOpenBisStorage(&cal0_storage, BisStorageId_Prodinfo)) || R_FAILED(fsStorageRead(&cal0_storage, 0, g_blank_cal0, ProdInfoSize))) {
std::abort();
}
fsStorageClose(&cal0_storage);
}
if (!IsCal0Valid(g_blank_cal0)) {
std::abort();
}
const char blank_serial[] = "XAW00000000000";
std::memcpy(&cal0[0x250], blank_serial, sizeof(blank_serial) - 1);
std::memcpy(&g_blank_cal0[0x250], blank_serial, sizeof(blank_serial) - 1);
static constexpr size_t NumErase = 7;
for (size_t i = 0; i < NumErase; i++) {
static constexpr size_t s_erase_offsets[NumErase] = {0xAE0, 0x3AE0, 0x35E1, 0x36E1, 0x2B0, 0x3D70, 0x3FC0};
static constexpr size_t s_erase_sizes[NumErase] = {0x800, 0x130, 0x6, 0x6, 0x180, 0x240, 0x240};
std::memset(&cal0[s_erase_offsets[i]], 0, s_erase_sizes[i]);
std::memset(&g_blank_cal0[s_erase_offsets[i]], 0, s_erase_sizes[i]);
}
static constexpr size_t NumHashes = 2;
{
static constexpr size_t s_hash_offsets[NumHashes] = {0x12E0, 0x20};
static constexpr size_t s_data_offsets[NumHashes] = {0xAE0, 0x40};
const size_t data_sizes[NumHashes] = {*reinterpret_cast<u32 *>(&cal0[0xAD0]), *reinterpret_cast<u32 *>(&cal0[0x8])};
const size_t data_sizes[NumHashes] = {*reinterpret_cast<u32 *>(&g_blank_cal0[0xAD0]), *reinterpret_cast<u32 *>(&g_blank_cal0[0x8])};
for (size_t i = 0; i < NumHashes; i++) {
u8 hash[SHA256_HASH_SIZE];
sha256CalculateHash(hash, &cal0[s_data_offsets[i]], data_sizes[i]);
std::memcpy(&cal0[s_hash_offsets[i]], hash, sizeof(hash));
sha256CalculateHash(hash, &g_blank_cal0[s_data_offsets[i]], data_sizes[i]);
std::memcpy(&g_blank_cal0[s_hash_offsets[i]], hash, sizeof(hash));
}
}
/* File creation is allowed to fail, because it may already exist. */
fsFsCreateFile(&g_sd_filesystem, BlankProdInfoPath, ProdInfoSize, 0);
FsFile f;
if (R_FAILED((rc = fsFsOpenFile(&g_sd_filesystem, BlankProdInfoPath, FS_OPEN_READ | FS_OPEN_WRITE, &f)))) {
std::abort();
}
if (R_FAILED((rc = fsFileSetSize(&f, ProdInfoSize)))) {
std::abort();
}
if (R_FAILED((rc = fsFileWrite(&f, 0, cal0, ProdInfoSize)))) {
std::abort();
}
if (R_FAILED((rc = fsFileFlush(&f)))) {
std::abort();
}
fsFileClose(&f);
if (R_FAILED((rc = fsFsOpenFile(&g_sd_filesystem, BlankProdInfoPath, FS_OPEN_READ, &g_blank_prodinfo_file)))) {
std::abort();
}
g_initialized_blank_cal0 = true;
}
bool Utils::IsCal0Valid(const u8 *cal0) {
char serial_number[0x40] = {0};
std::memcpy(serial_number, cal0 + 0x250, 0x18);
bool is_cal0_valid = true;
is_cal0_valid &= std::memcmp(g_cal0_backup, "CAL0", 4) == 0;
is_cal0_valid &= std::memcmp(g_cal0_backup + 0x250, serial_number, 0x18) == 0;
u32 cal0_size = ((u32 *)g_cal0_backup)[2];
is_cal0_valid &= std::memcmp(cal0, "CAL0", 4) == 0;
u32 cal0_size = ((u32 *)cal0)[2];
is_cal0_valid &= cal0_size + 0x40 <= ProdInfoSize;
if (is_cal0_valid) {
u8 calc_hash[0x20];
sha256CalculateHash(calc_hash, g_cal0_backup + 0x40, cal0_size);
is_cal0_valid &= std::memcmp(calc_hash, g_cal0_backup + 0x20, sizeof(calc_hash)) == 0;
sha256CalculateHash(calc_hash, cal0 + 0x40, cal0_size);
is_cal0_valid &= std::memcmp(calc_hash, cal0 + 0x20, sizeof(calc_hash)) == 0;
}
return is_cal0_valid;
@ -771,15 +747,7 @@ u16 Utils::GetCrc16(const void *data, size_t size) {
return crc16;
}
Result Utils::OpenBlankProdInfoFile(FsFile *out) {
if (!IsSdInitialized()) {
std::abort();
return ResultFsSdCardNotPresent;
}
Result rc = OpenSdFile(BlankProdInfoPath, FS_OPEN_READ, out);
if (R_FAILED((rc))) {
std::abort();
}
return rc;
}
const void *Utils::GetBlankProdInfoBuffer() {
EnsureBlankProdInfo();
return g_blank_cal0;
}

View file

@ -37,6 +37,8 @@ enum BisStorageId : u32 {
BisStorageId_SystemProperPartition = 33,
};
static constexpr size_t ProdInfoSize = 0x8000;
struct OverrideKey {
u64 key_combination;
bool override_by_default;
@ -61,7 +63,7 @@ class Utils {
static bool HasSdRomfsContent(u64 title_id);
static Result OpenBlankProdInfoFile(FsFile *out);
static const void *GetBlankProdInfoBuffer();
/* Delayed Initialization + MitM detection. */
static void InitializeThreadFunc(void *args);
@ -98,6 +100,6 @@ class Utils {
private:
static void RefreshConfiguration();
static void CreateBlankProdInfo();
static void EnsureBlankProdInfo();
static bool IsCal0Valid(const u8 *cal0);
};

@ -1 +1 @@
Subproject commit 8ec43f0d69463784c00ddb19c5f3b043e713548e
Subproject commit 5fe1dacee28265af5ed8272a3c9921904d6f50fe