diff options
| author | 2018-10-29 22:12:05 -0400 | |
|---|---|---|
| committer | 2018-11-15 12:48:09 -0500 | |
| commit | 056fa43dcdf69d1f9379e888f0c137b264e63709 (patch) | |
| tree | f22ab3f9e9e6fa125275ba1377753db442b561e8 /src/core | |
| parent | ldr_ro: Implement UnloadNrr (command 3) (diff) | |
| download | yuzu-056fa43dcdf69d1f9379e888f0c137b264e63709.tar.gz yuzu-056fa43dcdf69d1f9379e888f0c137b264e63709.tar.xz yuzu-056fa43dcdf69d1f9379e888f0c137b264e63709.zip | |
ldr_ro: Fully Implement LoadNro (command 0)
Includes NRO and BSS error checking, maximum loaded NRO check, NRR hash check, and proper remapping of BSS data.
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/hle/service/ldr/ldr.cpp | 121 |
1 files changed, 110 insertions, 11 deletions
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 252c66831..11b8a02e2 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp | |||
| @@ -231,29 +231,128 @@ public: | |||
| 231 | const VAddr bss_addr{rp.Pop<VAddr>()}; | 231 | const VAddr bss_addr{rp.Pop<VAddr>()}; |
| 232 | const u64 bss_size{rp.Pop<u64>()}; | 232 | const u64 bss_size{rp.Pop<u64>()}; |
| 233 | 233 | ||
| 234 | if (!initialized) { | ||
| 235 | LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); | ||
| 236 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 237 | rb.Push(ERROR_NOT_INITIALIZED); | ||
| 238 | return; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (nro.size() >= MAXIMUM_LOADED_RO) { | ||
| 242 | LOG_ERROR(Service_LDR, "Loading new NRO would exceed the maximum number of loaded NROs " | ||
| 243 | "(0x40)! Failing..."); | ||
| 244 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 245 | rb.Push(ERROR_MAXIMUM_NRO); | ||
| 246 | return; | ||
| 247 | } | ||
| 248 | |||
| 249 | // NRO Address does not fall on 0x1000 byte boundary | ||
| 250 | if ((nro_addr & 0xFFF) != 0) { | ||
| 251 | LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr); | ||
| 252 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 253 | rb.Push(ERROR_INVALID_ALIGNMENT); | ||
| 254 | return; | ||
| 255 | } | ||
| 256 | |||
| 257 | // NRO Size or BSS Size is zero or causes overflow | ||
| 258 | if (nro_addr + nro_size <= nro_addr || nro_size == 0 || (nro_size & 0xFFF) != 0 || | ||
| 259 | (bss_size != 0 && bss_addr + bss_size <= bss_addr) || | ||
| 260 | (std::numeric_limits<u64>::max() - nro_size < bss_size)) { | ||
| 261 | LOG_ERROR(Service_LDR, | ||
| 262 | "NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, " | ||
| 263 | "bss_address={:016X}, bss_size={:016X})", | ||
| 264 | nro_addr, nro_size, bss_addr, bss_size); | ||
| 265 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 266 | rb.Push(ERROR_INVALID_SIZE); | ||
| 267 | return; | ||
| 268 | } | ||
| 269 | |||
| 234 | // Read NRO data from memory | 270 | // Read NRO data from memory |
| 235 | std::vector<u8> nro_data(nro_size); | 271 | std::vector<u8> nro_data(nro_size); |
| 236 | Memory::ReadBlock(nro_addr, nro_data.data(), nro_size); | 272 | Memory::ReadBlock(nro_addr, nro_data.data(), nro_size); |
| 237 | 273 | ||
| 274 | SHA256Hash hash{}; | ||
| 275 | mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0); | ||
| 276 | |||
| 277 | // NRO Hash is already loaded | ||
| 278 | if (std::find_if(nro.begin(), nro.end(), [&hash](const std::pair<VAddr, NROInfo>& info) { | ||
| 279 | return info.second.hash == hash; | ||
| 280 | }) != nro.end()) { | ||
| 281 | LOG_ERROR(Service_LDR, "NRO is already loaded!"); | ||
| 282 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 283 | rb.Push(ERROR_ALREADY_LOADED); | ||
| 284 | return; | ||
| 285 | } | ||
| 286 | |||
| 287 | // NRO Hash is not in any loaded NRR | ||
| 288 | if (std::find_if(nrr.begin(), nrr.end(), | ||
| 289 | [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) { | ||
| 290 | return std::find(p.second.begin(), p.second.end(), hash) != | ||
| 291 | p.second.end(); | ||
| 292 | }) == nrr.end()) { | ||
| 293 | LOG_ERROR(Service_LDR, | ||
| 294 | "NRO hash is not present in any currently loaded NRRs (hash={})!", | ||
| 295 | Common::HexArrayToString(hash)); | ||
| 296 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 297 | rb.Push(ERROR_MISSING_NRR_HASH); | ||
| 298 | return; | ||
| 299 | } | ||
| 300 | |||
| 301 | NROHeader header; | ||
| 302 | std::memcpy(&header, nro_data.data(), sizeof(NROHeader)); | ||
| 303 | |||
| 304 | if (header.magic != Common::MakeMagic('N', 'R', 'O', '0') || header.nro_size != nro_size || | ||
| 305 | header.bss_size != bss_size || | ||
| 306 | header.ro_offset != header.text_offset + header.text_size || | ||
| 307 | header.rw_offset != header.ro_offset + header.ro_size || | ||
| 308 | nro_size != header.rw_offset + header.rw_size || (header.text_size & 0xFFF) != 0 || | ||
| 309 | (header.ro_size & 0xFFF) != 0 || (header.rw_size & 0xFFF) != 0) { | ||
| 310 | LOG_ERROR(Service_LDR, "NRO was invalid!"); | ||
| 311 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 312 | rb.Push(ERROR_INVALID_NRO); | ||
| 313 | return; | ||
| 314 | } | ||
| 315 | |||
| 238 | // Load NRO as new executable module | 316 | // Load NRO as new executable module |
| 239 | const VAddr addr{*Core::CurrentProcess()->VMManager().FindFreeRegion(nro_size + bss_size)}; | 317 | auto process = Core::CurrentProcess(); |
| 240 | Loader::AppLoader_NRO::LoadNro(nro_data, fmt::format("nro-{:08x}", addr), addr); | 318 | auto& vm_manager = process->VMManager(); |
| 319 | auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size); | ||
| 320 | |||
| 321 | ASSERT(map_address.Succeeded()); | ||
| 322 | |||
| 323 | ASSERT(process->MirrorMemory(*map_address, nro_addr, nro_size, | ||
| 324 | Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS); | ||
| 325 | ASSERT(process->UnmapMemory(nro_addr, 0, nro_size) == RESULT_SUCCESS); | ||
| 326 | |||
| 327 | if (bss_size > 0) { | ||
| 328 | ASSERT(process->MirrorMemory(*map_address + nro_size, bss_addr, bss_size, | ||
| 329 | Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS); | ||
| 330 | ASSERT(process->UnmapMemory(bss_addr, 0, bss_size) == RESULT_SUCCESS); | ||
| 331 | } | ||
| 332 | |||
| 333 | vm_manager.ReprotectRange(*map_address, header.text_size, | ||
| 334 | Kernel::VMAPermission::ReadExecute); | ||
| 335 | vm_manager.ReprotectRange(*map_address + header.ro_offset, header.ro_size, | ||
| 336 | Kernel::VMAPermission::Read); | ||
| 337 | vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size, | ||
| 338 | Kernel::VMAPermission::ReadWrite); | ||
| 339 | |||
| 340 | Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); | ||
| 341 | Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); | ||
| 342 | Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); | ||
| 343 | Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); | ||
| 241 | 344 | ||
| 242 | // TODO(bunnei): This is an incomplete implementation. It was tested with Super Mario Party. | 345 | nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size}); |
| 243 | // It is currently missing: | ||
| 244 | // - Signature checks with LoadNRR | ||
| 245 | // - Checking if a module has already been loaded | ||
| 246 | // - Using/validating BSS, etc. params (these are used from NRO header instead) | ||
| 247 | // - Error checking | ||
| 248 | // - ...Probably other things | ||
| 249 | 346 | ||
| 250 | IPC::ResponseBuilder rb{ctx, 4}; | 347 | IPC::ResponseBuilder rb{ctx, 4}; |
| 251 | rb.Push(RESULT_SUCCESS); | 348 | rb.Push(RESULT_SUCCESS); |
| 252 | rb.Push(addr); | 349 | rb.Push(*map_address); |
| 253 | LOG_WARNING(Service_LDR, "(STUBBED) called"); | 350 | } |
| 254 | } | 351 | } |
| 255 | 352 | ||
| 256 | void Initialize(Kernel::HLERequestContext& ctx) { | 353 | void Initialize(Kernel::HLERequestContext& ctx) { |
| 354 | initialized = true; | ||
| 355 | |||
| 257 | IPC::ResponseBuilder rb{ctx, 2}; | 356 | IPC::ResponseBuilder rb{ctx, 2}; |
| 258 | rb.Push(RESULT_SUCCESS); | 357 | rb.Push(RESULT_SUCCESS); |
| 259 | LOG_WARNING(Service_LDR, "(STUBBED) called"); | 358 | LOG_WARNING(Service_LDR, "(STUBBED) called"); |