fs.mitm: Implement bis protection

This commit is contained in:
Michael Scire
2019-11-28 01:28:20 -08:00
committed by SciresM
parent e1391d4162
commit f4ca2c02a7
30 changed files with 1517 additions and 19 deletions

View File

@@ -0,0 +1,80 @@
/*
* 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/>.
*/
#include "fs_shim.h"
#include "fs_mitm_service.hpp"
#include "fsmitm_boot0storage.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
namespace {
bool GetSettingsItemBooleanValue(const char *name, const char *key) {
u8 tmp = 0;
AMS_ASSERT(settings::fwdbg::GetSettingsItemValue(&tmp, sizeof(tmp), name, key) == sizeof(tmp));
return (tmp != 0);
}
}
Result FsMitmService::OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 _bis_partition_id) {
const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id);
/* Try to open a storage for the partition. */
FsStorage bis_storage;
R_TRY(fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id));
const sf::cmif::DomainObjectId target_object_id{bis_storage.s.object_id};
const bool is_sysmodule = ncm::IsSystemProgramId(this->client_info.program_id);
const bool is_hbl = this->client_info.override_status.IsHbl();
const bool can_write_bis = is_sysmodule || (is_hbl && GetSettingsItemBooleanValue("atmosphere", "enable_hbl_bis_write"));
const bool can_read_cal = is_sysmodule || (is_hbl && GetSettingsItemBooleanValue("atmosphere", "enable_hbl_cal_read"));
/* Allow HBL to write to boot1 (safe firm) + package2. */
/* This is needed to not break compatibility with ChoiDujourNX, which does not check for write access before beginning an update. */
/* TODO: get fixed so that this can be turned off without causing bricks :/ */
const bool is_package2 = (FsBisPartitionId_BootConfigAndPackage2Part1 <= bis_partition_id && bis_partition_id <= FsBisPartitionId_BootConfigAndPackage2Part6);
const bool is_boot1 = bis_partition_id == FsBisPartitionId_BootPartition2Root;
const bool can_write_bis_for_choi_support = is_hbl && (is_package2 || is_boot1);
/* Set output storage. */
if (bis_partition_id == FsBisPartitionId_BootPartition1Root) {
out.SetValue(std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->client_info)), target_object_id);
} else if (bis_partition_id == FsBisPartitionId_CalibrationBinary) {
/* PRODINFO should *never* be writable. */
/* If we have permissions, create a read only storage. */
if (can_read_cal) {
out.SetValue(std::make_shared<IStorageInterface>(new ReadOnlyStorageAdapter(new RemoteStorage(bis_storage))), target_object_id);
} else {
/* If we can't read cal, return permission denied. */
fsStorageClose(&bis_storage);
return fs::ResultPermissionDenied();
}
} else {
if (can_write_bis || can_write_bis_for_choi_support) {
/* We can write, so create a writable storage. */
out.SetValue(std::make_shared<IStorageInterface>(new RemoteStorage(bis_storage)), target_object_id);
} else {
/* We can only read, so create a readable storage. */
out.SetValue(std::make_shared<IStorageInterface>(new ReadOnlyStorageAdapter(new RemoteStorage(bis_storage))), target_object_id);
}
}
return ResultSuccess();
}
}

View File

@@ -16,26 +16,63 @@
#pragma once
#include <stratosphere.hpp>
#include "fsmitm_istorage_interface.hpp"
namespace ams::mitm::fs {
/* TODO: Consider re-enabling the mitm flag logic. */
class FsMitmService : public sf::IMitmServiceObject {
private:
enum class CommandId {
/* TODO */
OpenFileSystemDeprecated = 0,
SetCurrentProcess = 1,
OpenFileSystemWithPatch = 7,
OpenFileSystemWithId = 8,
OpenSdCardFileSystem = 18,
OpenSaveDataFileSystem = 51,
OpenBisStorage = 12,
OpenDataStorageByCurrentProcess = 200,
OpenDataStorageByDataId = 202,
};
public:
static bool ShouldMitm(const sm::MitmProcessInfo &client_info) {
/* TODO */
return false;
static std::atomic_bool has_launched_qlaunch = false;
/* TODO: intercepting everything seems to cause issues with sleep mode, for some reason. */
/* Figure out why, and address it. */
/* TODO: This may be because pre-rewrite code really mismanaged domain objects in a way that would cause bad things. */
/* Need to verify if this is fixed now. */
if (client_info.program_id == ncm::ProgramId::AppletQlaunch || client_info.program_id == ncm::ProgramId::AppletMaintenanceMenu) {
has_launched_qlaunch = true;
}
return has_launched_qlaunch || client_info.program_id == ncm::ProgramId::Ns || !ncm::IsSystemProgramId(client_info.program_id);
}
public:
SF_MITM_SERVICE_OBJECT_CTOR(FsMitmService) { /* ... */ }
protected:
/* TODO */
/* Overridden commands. */
/* Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 program_id, u32 filesystem_type); */
/* Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 program_id, u32 filesystem_type); */
/* Result OpenSdCardFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out); */
/* Result OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, FsSave save_struct); */
Result OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
/* Result OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out); */
/* Result OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out, u64 data_id, u8 storage_id); */
public:
DEFINE_SERVICE_DISPATCH_TABLE {
/* TODO */
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithPatch, hos::Version_200), */
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithId, hos::Version_200), */
/* MAKE_SERVICE_COMMAND_META(OpenSdCardFileSystem), */
/* MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), */
MAKE_SERVICE_COMMAND_META(OpenBisStorage),
/* MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess), */
/* MAKE_SERVICE_COMMAND_META(OpenDataStorageByDataId), */
};
};

View File

@@ -0,0 +1,25 @@
/*
* 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/>.
*/
#include "fs_shim.h"
/* Missing fsp-srv commands. */
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id) {
const u32 tmp = partition_id;
return serviceDispatchIn(s, 12, tmp,
.out_num_objects = 1,
.out_objects = &out->s,
);
}

View File

@@ -0,0 +1,19 @@
/**
* @file fs_shim.h
* @brief Filesystem Services (fs) IPC wrapper for fs.mitm.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Missing fsp-srv commands. */
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,120 @@
/*
* 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/>.
*/
#include "fsmitm_boot0storage.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
namespace {
os::Mutex g_boot0_access_mutex;
u8 g_boot0_bct_buffer[Boot0Storage::BctEndOffset];
}
bool Boot0Storage::CanModifyBctPublicKey() {
if (exosphere::IsRcmBugPatched()) {
/* RCM bug patched. */
/* Only allow NS to update the BCT pubks. */
/* AutoRCM on a patched unit will cause a brick, so homebrew should NOT be allowed to write. */
return this->client_info.program_id == ncm::ProgramId::Ns;
} else {
/* RCM bug unpatched. */
/* Allow homebrew but not NS to update the BCT pubks. */
return this->client_info.override_status.IsHbl();
}
}
Result Boot0Storage::Read(s64 offset, void *_buffer, size_t size) {
std::scoped_lock lk{g_boot0_access_mutex};
/* Check if we have nothing to do. */
if (size == 0) {
return ResultSuccess();
}
return Base::Read(offset, _buffer, size);
}
Result Boot0Storage::Write(s64 offset, const void *_buffer, size_t size) {
std::scoped_lock lk{g_boot0_access_mutex};
const u8 *buffer = static_cast<const u8 *>(_buffer);
/* Check if we have nothing to do. */
if (size == 0) {
return ResultSuccess();
}
/* Protect the EKS region from writes. */
if (offset <= EksStart) {
if (offset + size < EksStart) {
/* Fall through, no need to do anything here. */
} else {
if (offset + size > EksEnd) {
/* Perform portion of write falling past end of keyblobs. */
const s64 diff = EksEnd - offset;
R_TRY(Base::Write(EksEnd, buffer + diff, size - diff));
}
/* Adjust size to avoid writing end of data. */
size = EksStart - offset;
}
} else if (offset < EksEnd) {
if (offset + size <= EksEnd) {
/* Ignore writes falling strictly within the region. */
return ResultSuccess();
} else {
/* Only write past the end of the region. */
const s64 diff = EksEnd - offset;
buffer += diff;
size -= diff;
offset = EksEnd;
}
}
/* If we have nothing to write, succeed immediately. */
if (size == 0) {
return ResultSuccess();
}
/* We want to protect AutoRCM from NS on ipatched units. If we can modify bct pubks or we're not touching any of them, proceed. */
if (this->CanModifyBctPublicKey() || offset >= BctEndOffset || (util::AlignUp(offset, BctSize) >= BctEndOffset && (offset % BctSize) >= BctPubkEnd)) {
return Base::Write(offset, buffer, size);
}
/* Handle any data written past the end of the pubk region. */
if (offset + size > BctEndOffset) {
const u64 diff = BctEndOffset - offset;
R_TRY(Base::Write(BctEndOffset, buffer + diff, size - diff));
size = diff;
}
/* Read in the current BCT region. */
R_TRY(Base::Read(0, g_boot0_bct_buffer, BctEndOffset));
/* Update the bct buffer. */
for (u64 cur_offset = offset; cur_offset < BctEndOffset && cur_offset < offset + size; cur_offset++) {
const u64 cur_bct_relative_ofs = cur_offset % BctSize;
if (cur_bct_relative_ofs < BctPubkStart || BctPubkEnd <= cur_bct_relative_ofs) {
g_boot0_bct_buffer[cur_offset] = buffer[cur_offset - offset];
}
}
return Base::Write(0, g_boot0_bct_buffer, BctEndOffset);
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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 <stratosphere.hpp>
namespace ams::mitm::fs {
template<class Base, size_t SectorSize>
class SectoredStorageAdapter : public Base {
static_assert(std::is_base_of<ams::fs::IStorage, Base>::value);
private:
u8 sector_buf[SectorSize];
public:
/* Inherit constructors. */
using Base::Base;
public:
virtual Result Read(s64 offset, void *_buffer, size_t size) override {
u8 *buffer = static_cast<u8 *>(_buffer);
const s64 seek = util::AlignDown(offset, SectorSize);
const s64 sector_ofs = offset - seek;
/* Check if we have nothing to do. */
if (size == 0) {
return ResultSuccess();
}
/* Fast case. */
if (sector_ofs == 0 && util::IsAligned(size, SectorSize)) {
return Base::Read(offset, buffer, size);
}
R_TRY(Base::Read(seek, this->sector_buf, SectorSize));
if (size + sector_ofs <= SectorSize) {
/* Staying within the sector. */
std::memcpy(buffer, this->sector_buf + sector_ofs, size);
} else {
/* Leaving the sector. */
const size_t size_in_sector = SectorSize - sector_ofs;
std::memcpy(buffer, this->sector_buf + sector_ofs, size_in_sector);
size -= size_in_sector;
/* Read as many guaranteed aligned sectors as we can. */
const size_t aligned_remaining_size = util::AlignDown(size, SectorSize);
if (aligned_remaining_size) {
R_TRY(Base::Read(offset + size_in_sector, buffer + size_in_sector, aligned_remaining_size));
size -= aligned_remaining_size;
}
/* Read any leftover data. */
if (size) {
R_TRY(Base::Read(offset + size_in_sector + aligned_remaining_size, this->sector_buf, SectorSize));
std::memcpy(buffer + size_in_sector + aligned_remaining_size, this->sector_buf, size);
}
}
return ResultSuccess();
}
virtual Result Write(s64 offset, const void *_buffer, size_t size) override {
const u8 *buffer = static_cast<const u8 *>(_buffer);
const s64 seek = util::AlignDown(offset, SectorSize);
const s64 sector_ofs = offset - seek;
/* Check if we have nothing to do. */
if (size == 0) {
return ResultSuccess();
}
/* Fast case. */
if (sector_ofs == 0 && util::IsAligned(size, SectorSize)) {
return Base::Write(offset, buffer, size);
}
/* Load existing sector data. */
R_TRY(Base::Read(seek, this->sector_buf, SectorSize));
if (size + sector_ofs <= SectorSize) {
/* Staying within the sector. */
std::memcpy(this->sector_buf + sector_ofs, buffer, size);
R_TRY(Base::Write(seek, this->sector_buf, SectorSize));
} else {
/* Leaving the sector. */
const size_t size_in_sector = SectorSize - sector_ofs;
std::memcpy(this->sector_buf + sector_ofs, buffer, size_in_sector);
R_TRY(Base::Write(seek, this->sector_buf, SectorSize));
size -= size_in_sector;
/* Write as many guaranteed aligned sectors as we can. */
const size_t aligned_remaining_size = util::AlignDown(size, SectorSize);
if (aligned_remaining_size) {
R_TRY(Base::Write(offset + size_in_sector, buffer + size_in_sector, aligned_remaining_size));
size -= aligned_remaining_size;
}
/* Write any leftover data. */
if (size) {
R_TRY(Base::Read(offset + size_in_sector + aligned_remaining_size, this->sector_buf, SectorSize));
std::memcpy(this->sector_buf, buffer + size_in_sector + aligned_remaining_size, size);
R_TRY(Base::Write(offset + size_in_sector + aligned_remaining_size, this->sector_buf, SectorSize));
}
}
return ResultSuccess();
}
};
/* Represents an RCM-preserving BOOT0 partition. */
class Boot0Storage : public SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200> {
public:
using Base = SectoredStorageAdapter<ams::fs::RemoteStorage, 0x200>;
static constexpr s64 BctEndOffset = 0xFC000;
static constexpr s64 BctSize = static_cast<s64>(ams::updater::BctSize);
static constexpr s64 BctPubkStart = 0x210;
static constexpr s64 BctPubkSize = 0x100;
static constexpr s64 BctPubkEnd = BctPubkStart + BctPubkSize;
static constexpr s64 EksStart = 0x180000;
static constexpr s64 EksSize = static_cast<s64>(ams::updater::EksSize);
static constexpr s64 EksEnd = EksStart + EksSize;
private:
sm::MitmProcessInfo client_info;
private:
bool CanModifyBctPublicKey();
public:
Boot0Storage(FsStorage *s, const sm::MitmProcessInfo &c) : Base(s), client_info(c) { /* ... */ }
Boot0Storage(FsStorage s, const sm::MitmProcessInfo &c) : Base(s), client_info(c) { /* ... */ }
public:
virtual Result Read(s64 offset, void *_buffer, size_t size) override;
virtual Result Write(s64 offset, const void *_buffer, size_t size) override;
};
}

View File

@@ -0,0 +1,63 @@
/*
* 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/>.
*/
#include "fsmitm_istorage_interface.hpp"
namespace ams::mitm::fs {
using namespace ams::fs;
Result IStorageInterface::Read(s64 offset, const sf::OutNonSecureBuffer &buffer, s64 size) {
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(size >= 0, fs::ResultInvalidSize());
return this->base_storage->Read(offset, buffer.GetPointer(), size);
}
Result IStorageInterface::Write(s64 offset, const sf::InNonSecureBuffer &buffer, s64 size) {
/* TODO: N increases thread priority temporarily when writing. We may want to eventually. */
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(size >= 0, fs::ResultInvalidSize());
return this->base_storage->Write(offset, buffer.GetPointer(), size);
}
Result IStorageInterface::Flush() {
return this->base_storage->Flush();
}
Result IStorageInterface::SetSize(s64 size) {
R_UNLESS(size >= 0, fs::ResultInvalidSize());
return this->base_storage->SetSize(size);
}
Result IStorageInterface::GetSize(sf::Out<s64> out) {
return this->base_storage->GetSize(out.GetPointer());
}
Result IStorageInterface::OperateRange(sf::Out<StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) {
/* N includes this redundant check, so we will too. */
R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument());
out->Clear();
if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) {
fs::StorageQueryRangeInfo info;
R_TRY(this->base_storage->OperateRange(&info, sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0));
out->Merge(info);
}
return ResultSuccess();
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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 <stratosphere.hpp>
namespace ams::mitm::fs {
class IStorageInterface : public sf::IServiceObject {
private:
enum class CommandId {
Read = 0,
Write = 1,
Flush = 2,
SetSize = 3,
GetSize = 4,
OperateRange = 5,
};
private:
std::unique_ptr<ams::fs::IStorage> base_storage;
public:
IStorageInterface(ams::fs::IStorage *s) : base_storage(s) { /* ... */ }
IStorageInterface(std::unique_ptr<ams::fs::IStorage> s) : base_storage(std::move(s)) { /* ... */ }
private:
/* Command API. */
virtual Result Read(s64 offset, const sf::OutNonSecureBuffer &buffer, s64 size) final;
virtual Result Write(s64 offset, const sf::InNonSecureBuffer &buffer, s64 size) final;
virtual Result Flush() final;
virtual Result SetSize(s64 size) final;
virtual Result GetSize(sf::Out<s64> out) final;
virtual Result OperateRange(sf::Out<ams::fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) final;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
/* 1.0.0- */
MAKE_SERVICE_COMMAND_META(Read),
MAKE_SERVICE_COMMAND_META(Write),
MAKE_SERVICE_COMMAND_META(Flush),
MAKE_SERVICE_COMMAND_META(SetSize),
MAKE_SERVICE_COMMAND_META(GetSize),
/* 4.0.0- */
MAKE_SERVICE_COMMAND_META(OperateRange, hos::Version_400),
};
};
}

View File

@@ -322,6 +322,14 @@ namespace ams::settings::fwdbg {
/* Set to "normal" for normal reboot, "rcm" for rcm reboot. */
R_ASSERT(ParseSettingsItemValue("atmosphere", "power_menu_reboot_function", "str!payload"));
/* Enable writing to BIS partitions for HBL. */
/* This is probably undesirable for normal usage. */
R_ASSERT(ParseSettingsItemValue("atmosphere", "enable_hbl_bis_write", "u8!0x0"));
/* Enable HBL to read the CAL0 partition. */
/* This is probably undesirable for normal usage. */
R_ASSERT(ParseSettingsItemValue("atmosphere", "enable_hbl_cal_read", "u8!0x0"));
/* Controls whether dmnt cheats should be toggled on or off by */
/* default. 1 = toggled on by default, 0 = toggled off by default. */
R_ASSERT(ParseSettingsItemValue("atmosphere", "dmnt_cheats_enabled_by_default", "u8!0x1"));