/*
* Copyright (c) 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 .
*/
#include
#include "ldr_capabilities.hpp"
namespace ams::ldr {
namespace {
/* Types. */
enum class CapabilityId {
KernelFlags = 3,
SyscallMask = 4,
MapRange = 6,
MapPage = 7,
MapRegion = 10,
InterruptPair = 11,
ApplicationType = 13,
KernelVersion = 14,
HandleTable = 15,
DebugFlags = 16,
Empty = 32,
};
template
using CapabilityField = util::BitPack32::Field;
#define DEFINE_CAPABILITY_FIELD(name, prev, ...) \
using name = CapabilityField; \
constexpr ALWAYS_INLINE typename name::Type Get##name() const { return this->Get(); }
constexpr ALWAYS_INLINE CapabilityId GetCapabilityId(util::BitPack32 cap) {
return static_cast(util::CountTrailingZeros(~cap.value));
}
constexpr inline util::BitPack32 EmptyCapability = {~u32{}};
static_assert(GetCapabilityId(EmptyCapability) == CapabilityId::Empty);
#define CAPABILITY_CLASS_NAME(id) Capability##id
#define DEFINE_CAPABILITY_CLASS(id, member_functions) \
class CAPABILITY_CLASS_NAME(id) { \
public: \
static constexpr CapabilityId Id = CapabilityId::id; \
using IdBits = CapabilityField<0, static_cast(Id) + 1>; \
static constexpr u32 IdBitsValue = (static_cast(1) << static_cast(Id)) - 1; \
private: \
util::BitPack32 m_value; \
private: \
template \
constexpr ALWAYS_INLINE typename FieldType::Type Get() const { return m_value.Get(); } \
template \
constexpr ALWAYS_INLINE void Set(typename FieldType::Type fv) { m_value.Set(fv); } \
constexpr ALWAYS_INLINE u32 GetValue() const { return m_value.value; } \
public: \
constexpr ALWAYS_INLINE CAPABILITY_CLASS_NAME(id)(util::BitPack32 v) : m_value{v} { /* ... */ } \
\
static constexpr CAPABILITY_CLASS_NAME(id) Decode(util::BitPack32 v) { return CAPABILITY_CLASS_NAME(id)(v); } \
\
member_functions \
}; \
static_assert(std::is_trivially_destructible::value)
/* Class definitions. */
DEFINE_CAPABILITY_CLASS(KernelFlags,
DEFINE_CAPABILITY_FIELD(MaximumThreadPriority, IdBits, 6);
DEFINE_CAPABILITY_FIELD(MinimumThreadPriority, MaximumThreadPriority, 6);
DEFINE_CAPABILITY_FIELD(MinimumCoreId, MinimumThreadPriority, 8);
DEFINE_CAPABILITY_FIELD(MaximumCoreId, MinimumCoreId, 8);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (this->GetMinimumThreadPriority() < restriction.GetMinimumThreadPriority() ||
this->GetMaximumThreadPriority() > restriction.GetMaximumThreadPriority() ||
this->GetMinimumThreadPriority() > this->GetMaximumThreadPriority()) {
return false;
}
if (this->GetMinimumCoreId() < restriction.GetMinimumCoreId() ||
this->GetMaximumCoreId() > restriction.GetMaximumCoreId() ||
this->GetMinimumCoreId() > this->GetMaximumCoreId()) {
return false;
}
return true;
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(SyscallMask,
DEFINE_CAPABILITY_FIELD(Mask, IdBits, 24);
DEFINE_CAPABILITY_FIELD(Index, Mask, 3);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (this->GetIndex() == restriction.GetIndex() && this->GetMask() == restriction.GetMask()) {
return true;
}
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(MapRange,
DEFINE_CAPABILITY_FIELD(AddressSize, IdBits, 24);
DEFINE_CAPABILITY_FIELD(Flag, AddressSize, 1, bool);
static constexpr size_t SizeMax = 0x100000;
bool IsValid(const util::BitPack32 next_cap, const util::BitPack32 *kac, size_t kac_count) const {
if (GetCapabilityId(next_cap) != Id) {
return false;
}
const auto next = Decode(next_cap);
const u32 start = this->GetAddressSize();
const u32 size = next.GetAddressSize();
const u32 end = start + size;
if (size >= SizeMax) {
return false;
}
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i++]);
if (i >= kac_count || GetCapabilityId(kac[i]) != Id) {
return false;
}
const auto restriction_next = Decode(kac[i]);
const u32 restriction_start = restriction.GetAddressSize();
const u32 restriction_size = restriction_next.GetAddressSize();
const u32 restriction_end = restriction_start + restriction_size;
if (restriction_size >= SizeMax) {
continue;
}
if (this->GetFlag() == restriction.GetFlag() && next.GetFlag() == restriction_next.GetFlag()) {
if (restriction_start <= start && start <= restriction_end && end <= restriction_end) {
return true;
}
}
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(MapPage,
DEFINE_CAPABILITY_FIELD(Address, IdBits, 24);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (this->GetValue() == restriction.GetValue()) {
return true;
}
}
}
return false;
}
);
enum class MemoryRegionType : u32 {
NoMapping = 0,
KernelTraceBuffer = 1,
OnMemoryBootImage = 2,
DTB = 3,
};
DEFINE_CAPABILITY_CLASS(MapRegion,
DEFINE_CAPABILITY_FIELD(Region0, IdBits, 6, MemoryRegionType);
DEFINE_CAPABILITY_FIELD(ReadOnly0, Region0, 1, bool);
DEFINE_CAPABILITY_FIELD(Region1, ReadOnly0, 6, MemoryRegionType);
DEFINE_CAPABILITY_FIELD(ReadOnly1, Region1, 1, bool);
DEFINE_CAPABILITY_FIELD(Region2, ReadOnly1, 6, MemoryRegionType);
DEFINE_CAPABILITY_FIELD(ReadOnly2, Region2, 1, bool);
static bool IsValidRegionType(const util::BitPack32 *kac, size_t kac_count, MemoryRegionType region_type, bool is_read_only) {
if (region_type != MemoryRegionType::NoMapping) {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if ((restriction.GetRegion0() == region_type && (is_read_only || !restriction.GetReadOnly0())) ||
(restriction.GetRegion1() == region_type && (is_read_only || !restriction.GetReadOnly1())) ||
(restriction.GetRegion2() == region_type && (is_read_only || !restriction.GetReadOnly2())))
{
return true;
}
}
}
return false;
} else {
return true;
}
}
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
return IsValidRegionType(kac, kac_count, this->GetRegion0(), this->GetReadOnly0()) &&
IsValidRegionType(kac, kac_count, this->GetRegion1(), this->GetReadOnly1()) &&
IsValidRegionType(kac, kac_count, this->GetRegion2(), this->GetReadOnly2());
}
);
DEFINE_CAPABILITY_CLASS(InterruptPair,
DEFINE_CAPABILITY_FIELD(InterruptId0, IdBits, 10);
DEFINE_CAPABILITY_FIELD(InterruptId1, InterruptId0, 10);
static constexpr u32 EmptyInterruptId = 0x3FF;
bool IsSingleIdValid(const u32 id, const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
if (restriction.GetInterruptId0() == EmptyInterruptId && restriction.GetInterruptId1() == EmptyInterruptId) {
return true;
}
if (restriction.GetInterruptId0() == id || restriction.GetInterruptId1() == id) {
return true;
}
}
}
return false;
}
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
return IsSingleIdValid(this->GetInterruptId0(), kac, kac_count) && IsSingleIdValid(this->GetInterruptId1(), kac, kac_count);
}
);
DEFINE_CAPABILITY_CLASS(ApplicationType,
DEFINE_CAPABILITY_FIELD(ApplicationType, IdBits, 3);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return restriction.GetValue() == this->GetValue();
}
}
return false;
}
static constexpr util::BitPack32 Encode(u32 app_type) {
util::BitPack32 encoded{IdBitsValue};
encoded.Set(app_type);
return encoded;
}
);
DEFINE_CAPABILITY_CLASS(KernelVersion,
DEFINE_CAPABILITY_FIELD(MinorVersion, IdBits, 4);
DEFINE_CAPABILITY_FIELD(MajorVersion, MinorVersion, 13);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return restriction.GetValue() == this->GetValue();
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(HandleTable,
DEFINE_CAPABILITY_FIELD(Size, IdBits, 10);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return this->GetSize() <= restriction.GetSize();
}
}
return false;
}
);
DEFINE_CAPABILITY_CLASS(DebugFlags,
DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool);
DEFINE_CAPABILITY_FIELD(ForceDebugProd, AllowDebug, 1, bool);
DEFINE_CAPABILITY_FIELD(ForceDebug, ForceDebugProd, 1, bool);
bool IsValid(const util::BitPack32 *kac, size_t kac_count) const {
u32 total = 0;
if (this->GetAllowDebug()) { ++total; }
if (this->GetForceDebugProd()) { ++total; }
if (this->GetForceDebug()) { ++total; }
if (total > 1) {
return false;
}
for (size_t i = 0; i < kac_count; i++) {
if (GetCapabilityId(kac[i]) == Id) {
const auto restriction = Decode(kac[i]);
return (restriction.GetValue() & this->GetValue()) == this->GetValue();
}
}
return false;
}
static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug_prod, bool force_debug) {
util::BitPack32 encoded{IdBitsValue};
encoded.Set(allow_debug);
encoded.Set(force_debug_prod);
encoded.Set(force_debug);
return encoded;
}
);
}
/* Capabilities API. */
Result TestCapability(const util::BitPack32 *kacd, size_t kacd_count, const util::BitPack32 *kac, size_t kac_count) {
for (size_t i = 0; i < kac_count; i++) {
const auto cap = kac[i];
const auto id = GetCapabilityId(cap);
#define VALIDATE_CASE(id) \
case CapabilityId::id: \
R_UNLESS(Capability##id::Decode(cap).IsValid(kacd, kacd_count), ldr::ResultInvalidCapability##id()); \
break
switch (id) {
VALIDATE_CASE(KernelFlags);
VALIDATE_CASE(SyscallMask);
VALIDATE_CASE(MapPage);
VALIDATE_CASE(MapRegion);
VALIDATE_CASE(InterruptPair);
VALIDATE_CASE(ApplicationType);
VALIDATE_CASE(KernelVersion);
VALIDATE_CASE(HandleTable);
VALIDATE_CASE(DebugFlags);
case CapabilityId::MapRange:
{
/* Map Range needs extra logic because there it involves two sequential caps. */
i++;
R_UNLESS(i < kac_count, ldr::ResultInvalidCapabilityMapRange());
R_UNLESS(CapabilityMapRange::Decode(cap).IsValid(kac[i], kacd, kacd_count), ldr::ResultInvalidCapabilityMapRange());
}
break;
default:
R_UNLESS(id == CapabilityId::Empty, ldr::ResultUnknownCapability());
break;
}
#undef VALIDATE_CASE
}
R_SUCCEED();
}
u16 MakeProgramInfoFlag(const util::BitPack32 *kac, size_t count) {
u16 flags = 0;
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
case CapabilityId::ApplicationType:
{
const auto app_type = CapabilityApplicationType::Decode(cap).GetApplicationType() & ProgramInfoFlag_ApplicationTypeMask;
if (app_type != ProgramInfoFlag_InvalidType) {
flags |= app_type;
}
}
break;
case CapabilityId::DebugFlags:
if (CapabilityDebugFlags::Decode(cap).GetAllowDebug()) {
flags |= ProgramInfoFlag_AllowDebug;
}
break;
default:
break;
}
}
return flags;
}
void UpdateProgramInfoFlag(u16 flags, util::BitPack32 *kac, size_t count) {
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
case CapabilityId::ApplicationType:
kac[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask);
break;
case CapabilityId::DebugFlags:
kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebugProd(), CapabilityDebugFlags::Decode(cap).GetForceDebug());
break;
default:
break;
}
}
}
void PreProcessCapability(util::BitPack32 *kac, size_t count) {
for (size_t i = 0; i < count; ++i) {
const auto cap = kac[i];
switch (GetCapabilityId(cap)) {
/* NOTE: Currently, there is no pre-processing necessary. */
default:
break;
}
}
}
}