mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-02 12:56:05 +00:00
SSL_select_next_proto validated both lists and threw for an empty list, a zero-length entry or a truncated entry. The firmware 13.42 report on PR #9785 shows the module runs LibreSSL 3.4.1's loop with no validation: a zero-length entry is negotiated, no overlap returns the first supported entry with its unchecked length byte, even for an empty supported list, and an empty peer list has no overlap. Follow that loop, and throw only for the null pointers the module would read. The test places each reported list before a guard page and checks the six reported cases. Co-Authored-By: Claude Fable 5.1 <[email protected]>
232 lines
8.9 KiB
C++
232 lines
8.9 KiB
C++
#include "prx/libSceLibreSsl/include/Api.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <bit>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <initializer_list>
|
|
#include <source_location>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
using SelectProtocol = int (APS5_VABI*)(unsigned char**, unsigned char*, const unsigned char*, std::uint32_t, const unsigned char*, std::uint32_t);
|
|
using Protocol = std::vector<unsigned char>;
|
|
using Protocols = std::vector<Protocol>;
|
|
|
|
void Require(bool condition, std::source_location location = std::source_location::current()) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "LibreSSL protocol check failed at line %u\n", location.line());
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
template<typename TAction> void RequireThrows(TAction action, std::source_location location = std::source_location::current()) {
|
|
try { action(); } catch (const std::exception&) { return; }
|
|
Require(false, location);
|
|
}
|
|
|
|
class GuardedPages {
|
|
public:
|
|
GuardedPages() {
|
|
#ifdef _WIN32
|
|
SYSTEM_INFO info{};
|
|
GetSystemInfo(&info);
|
|
page_ = info.dwPageSize;
|
|
base_ = static_cast<unsigned char*>(VirtualAlloc(nullptr, 2 * page_, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
Require(base_ != nullptr);
|
|
DWORD previous = 0;
|
|
Require(VirtualProtect(base_ + page_, page_, PAGE_NOACCESS, &previous) != 0);
|
|
#else
|
|
page_ = static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
|
void* const mapped = mmap(nullptr, 2 * page_, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
Require(mapped != MAP_FAILED);
|
|
base_ = static_cast<unsigned char*>(mapped);
|
|
Require(mprotect(base_ + page_, page_, PROT_NONE) == 0);
|
|
#endif
|
|
}
|
|
|
|
~GuardedPages() {
|
|
#ifdef _WIN32
|
|
VirtualFree(base_, 0, MEM_RELEASE);
|
|
#else
|
|
munmap(base_, 2 * page_);
|
|
#endif
|
|
}
|
|
|
|
GuardedPages(const GuardedPages&) = delete;
|
|
GuardedPages& operator=(const GuardedPages&) = delete;
|
|
|
|
unsigned char* End() const { return base_ + page_; }
|
|
|
|
unsigned char* Place(std::initializer_list<unsigned char> bytes) {
|
|
unsigned char* const start = End() - bytes.size();
|
|
std::copy(bytes.begin(), bytes.end(), start);
|
|
return start;
|
|
}
|
|
|
|
private:
|
|
unsigned char* base_ = nullptr;
|
|
std::size_t page_ = 0;
|
|
};
|
|
|
|
Protocol Encode(const Protocols& names) {
|
|
Protocol result;
|
|
for (const auto& name : names) {
|
|
Require(!name.empty() && name.size() <= 255);
|
|
result.push_back(static_cast<unsigned char>(name.size()));
|
|
result.insert(result.end(), name.begin(), name.end());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void CheckSelection(SelectProtocol select, const Protocols& peerNames, const Protocols& supportedNames) {
|
|
const auto peer = Encode(peerNames);
|
|
const auto supported = Encode(supportedNames);
|
|
const auto peerCopy = peer;
|
|
const auto supportedCopy = supported;
|
|
const unsigned char* expected = supported.data() + 1;
|
|
std::size_t expectedLength = supportedNames.front().size();
|
|
int expectedStatus = 2;
|
|
std::size_t offset = 1;
|
|
for (const auto& name : peerNames) {
|
|
if (std::find(supportedNames.begin(), supportedNames.end(), name) != supportedNames.end()) {
|
|
expected = peer.data() + offset;
|
|
expectedLength = name.size();
|
|
expectedStatus = 1;
|
|
break;
|
|
}
|
|
offset += name.size() + 1;
|
|
}
|
|
unsigned char* output = nullptr;
|
|
std::array<unsigned char, 3> length{0xC1, 0xA5, 0xC2};
|
|
Require(select(&output, &length[1], peer.data(), static_cast<std::uint32_t>(peer.size()),
|
|
supported.data(), static_cast<std::uint32_t>(supported.size())) == expectedStatus);
|
|
Require(output == expected && length[1] == expectedLength);
|
|
Require(length[0] == 0xC1 && length[2] == 0xC2);
|
|
Require(peer == peerCopy && supported == supportedCopy);
|
|
}
|
|
|
|
void CheckRaw(SelectProtocol select, const unsigned char* peer, std::uint32_t peerLength, const unsigned char* supported,
|
|
std::uint32_t supportedLength, int expectedStatus, const unsigned char* expectedOutput, unsigned char expectedLength,
|
|
std::source_location location = std::source_location::current()) {
|
|
unsigned char* output = nullptr;
|
|
unsigned char length = 0xA5;
|
|
Require(select(&output, &length, peer, peerLength, supported, supportedLength) == expectedStatus, location);
|
|
Require(output == expectedOutput && length == expectedLength, location);
|
|
}
|
|
|
|
void RunValid(SelectProtocol select) {
|
|
const Protocol http{'h', 't', 't', 'p', '/', '1', '.', '1'};
|
|
const Protocol h2{'h', '2'};
|
|
const Protocol h3{'h', '3'};
|
|
CheckSelection(select, {h2, http}, {http, h2});
|
|
CheckSelection(select, {http, h2}, {h2, http});
|
|
CheckSelection(select, {h3}, {http, h2});
|
|
CheckSelection(select, {h2, h2, http}, {http, h2, h2});
|
|
CheckSelection(select, {{0, 0xFF, 0x80}, {'a', 0, 'b'}}, {{'a', 0, 'b'}, {0, 0xFF, 0x80}});
|
|
CheckSelection(select, {{'h', '2', 0}, {'H', '2'}}, {h2, http});
|
|
Protocol longest(255);
|
|
for (std::size_t index = 0; index < longest.size(); ++index) longest[index] = static_cast<unsigned char>(index);
|
|
CheckSelection(select, {longest}, {h2, longest});
|
|
CheckSelection(select, {h2}, {longest});
|
|
|
|
const Protocols alphabet{{'a'}, {'a', 'b'}, {'b'}, {0, 0xFF}};
|
|
std::vector<Protocols> lists;
|
|
for (std::size_t count = 1, combinations = alphabet.size(); count <= 3; ++count, combinations *= alphabet.size()) {
|
|
for (std::size_t code = 0; code < combinations; ++code) {
|
|
Protocols names;
|
|
for (std::size_t index = 0, remaining = code; index < count; ++index, remaining /= alphabet.size())
|
|
names.push_back(alphabet[remaining % alphabet.size()]);
|
|
lists.push_back(std::move(names));
|
|
}
|
|
}
|
|
for (const auto& peer : lists)
|
|
for (const auto& supported : lists) CheckSelection(select, peer, supported);
|
|
|
|
Protocols large(33000, Protocol{'x'});
|
|
large.push_back(h2);
|
|
CheckSelection(select, large, {h2});
|
|
CheckSelection(select, {h2}, large);
|
|
}
|
|
|
|
void RunReported(SelectProtocol select) {
|
|
GuardedPages peerPages;
|
|
GuardedPages supportedPages;
|
|
{
|
|
const auto* peer = peerPages.Place({0, 2, 'h', '2'});
|
|
const auto* supported = supportedPages.Place({0});
|
|
CheckRaw(select, peer, 4, supported, 1, 1, peer + 1, 0);
|
|
}
|
|
{
|
|
const auto* peer = peerPages.Place({0, 2, 'h', '2'});
|
|
const auto* supported = supportedPages.Place({2, 'h', '2'});
|
|
CheckRaw(select, peer, 4, supported, 3, 1, peer + 2, 2);
|
|
}
|
|
{
|
|
const auto* peer = peerPages.Place({5, 'a', 'b'});
|
|
const auto* supported = supportedPages.Place({2, 'a', 'b'});
|
|
CheckRaw(select, peer, 3, supported, 3, 2, supported + 1, 2);
|
|
}
|
|
{
|
|
const auto* peer = peerPages.Place({2, 'h', '2'});
|
|
const auto* supported = supportedPages.Place({5, 'a', 'b'});
|
|
CheckRaw(select, peer, 3, supported, 3, 2, supported + 1, 5);
|
|
}
|
|
{
|
|
const auto* supported = supportedPages.Place({2, 'h', '2'});
|
|
CheckRaw(select, peerPages.End(), 0, supported, 3, 2, supported + 1, 2);
|
|
CheckRaw(select, nullptr, 0, supported, 3, 2, supported + 1, 2);
|
|
}
|
|
{
|
|
const auto* peer = peerPages.Place({2, 'h', '2'});
|
|
const auto* supported = supportedPages.Place({3, 'x', 'y', 'z'});
|
|
CheckRaw(select, peer, 3, supported, 0, 2, supported + 1, 3);
|
|
}
|
|
unsigned char* output = nullptr;
|
|
unsigned char length = 0;
|
|
const unsigned char list[]{2, 'h', '2'};
|
|
RequireThrows([&] { select(nullptr, &length, list, 3, list, 3); });
|
|
RequireThrows([&] { select(&output, nullptr, list, 3, list, 3); });
|
|
RequireThrows([&] { select(&output, &length, nullptr, 3, list, 3); });
|
|
RequireThrows([&] { select(&output, &length, list, 3, nullptr, 3); });
|
|
RequireThrows([&] { select(&output, &length, list, 3, nullptr, 0); });
|
|
Require(output == nullptr && length == 0);
|
|
}
|
|
|
|
void Run(SelectProtocol select) {
|
|
RunValid(select);
|
|
RunReported(select);
|
|
std::printf("LibreSSL protocol selection: 7056 generated pairs, valid-list boundaries and the reported unvalidated lists passed\n");
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
Require(argc == 3);
|
|
#ifdef _WIN32
|
|
const auto library = LoadLibraryExW(std::filesystem::path(argv[1]).c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
|
|
if (!library) std::fprintf(stderr, "LoadLibraryExW failed: %lu\n", GetLastError());
|
|
Require(library != nullptr);
|
|
const auto select = std::bit_cast<SelectProtocol>(GetProcAddress(library, argv[2]));
|
|
#else
|
|
const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL);
|
|
if (!library) std::fprintf(stderr, "%s\n", dlerror());
|
|
Require(library != nullptr);
|
|
const auto select = reinterpret_cast<SelectProtocol>(dlsym(library, argv[2]));
|
|
#endif
|
|
Require(select != nullptr);
|
|
Run(select);
|
|
}
|