IOS/ES: Fix the implementation of ES_DeleteTicket

* It should take a ticket view, not a title ID.
* It's missing a lot of checks.
* It's not deleting tickets properly.
* It's not deleting only the ticket it needs to delete.
* It should not return -1017 when the ticket doesn't exist.
* It's not returning the proper error code when a read/write fails.
* It's not cleaning up the ticket directory if there is nothing left.

This commit fixes its implementation.
This commit is contained in:
Léo Lam 2017-05-05 00:12:37 +02:00
parent a7680a3d1a
commit 5fb2ad2b3a
3 changed files with 55 additions and 6 deletions

View File

@ -300,6 +300,21 @@ std::vector<u8> TicketReader::GetTitleKey() const
return key; return key;
} }
void TicketReader::DeleteTicket(u64 ticket_id_to_delete)
{
std::vector<u8> new_ticket;
const size_t num_tickets = GetNumberOfTickets();
for (size_t i = 0; i < num_tickets; ++i)
{
const auto ticket_start = m_bytes.cbegin() + sizeof(Ticket) * i;
const u64 ticket_id = Common::swap64(&*ticket_start + offsetof(Ticket, ticket_id));
if (ticket_id != ticket_id_to_delete)
new_ticket.insert(new_ticket.end(), ticket_start, ticket_start + sizeof(Ticket));
}
m_bytes = std::move(new_ticket);
}
s32 TicketReader::Unpersonalise() s32 TicketReader::Unpersonalise()
{ {
const auto ticket_begin = m_bytes.begin(); const auto ticket_begin = m_bytes.begin();

View File

@ -193,6 +193,9 @@ public:
u64 GetTitleId() const; u64 GetTitleId() const;
std::vector<u8> GetTitleKey() const; std::vector<u8> GetTitleKey() const;
// Deletes a ticket with the given ticket ID from the internal buffer.
void DeleteTicket(u64 ticket_id);
// Decrypts the title key field for a "personalised" ticket -- one that is device-specific // Decrypts the title key field for a "personalised" ticket -- one that is device-specific
// and has a title key that must be decrypted first. // and has a title key that must be decrypted first.
s32 Unpersonalise(); s32 Unpersonalise();

View File

@ -6,6 +6,7 @@
#include <algorithm> #include <algorithm>
#include <cinttypes> #include <cinttypes>
#include <cstddef>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -335,15 +336,45 @@ IPCCommandResult ES::DeleteTitle(const IOCtlVRequest& request)
IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request) IPCCommandResult ES::DeleteTicket(const IOCtlVRequest& request)
{ {
if (!request.HasNumberOfValidVectors(1, 0)) if (!request.HasNumberOfValidVectors(1, 0) ||
request.in_vectors[0].size != sizeof(IOS::ES::TicketView))
{
return GetDefaultReply(ES_EINVAL);
}
const u64 title_id =
Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, title_id));
if (!CanDeleteTitle(title_id))
return GetDefaultReply(ES_EINVAL); return GetDefaultReply(ES_EINVAL);
u64 TitleID = Memory::Read_U64(request.in_vectors[0].address); auto ticket = DiscIO::FindSignedTicket(title_id);
INFO_LOG(IOS_ES, "IOCTL_ES_DELETETICKET: title: %08x/%08x", (u32)(TitleID >> 32), (u32)TitleID); if (!ticket.IsValid())
return GetDefaultReply(FS_ENOENT);
// Presumably return -1017 when delete fails const u64 ticket_id =
if (!File::Delete(Common::GetTicketFileName(TitleID, Common::FROM_SESSION_ROOT))) Memory::Read_U64(request.in_vectors[0].address + offsetof(IOS::ES::TicketView, ticket_id));
return GetDefaultReply(ES_EINVAL); ticket.DeleteTicket(ticket_id);
const std::vector<u8>& new_ticket = ticket.GetRawTicket();
const std::string ticket_path = Common::GetTicketFileName(title_id, Common::FROM_SESSION_ROOT);
{
File::IOFile ticket_file(ticket_path, "wb");
if (!ticket_file || !ticket_file.WriteBytes(new_ticket.data(), new_ticket.size()))
return GetDefaultReply(ES_EIO);
}
// Delete the ticket file if it is now empty.
if (new_ticket.empty())
File::Delete(ticket_path);
// Delete the ticket directory if it is now empty.
const std::string ticket_parent_dir =
Common::RootUserPath(Common::FROM_CONFIGURED_ROOT) +
StringFromFormat("/ticket/%08x", static_cast<u32>(title_id >> 32));
const auto ticket_parent_dir_entries = File::ScanDirectoryTree(ticket_parent_dir, false);
if (ticket_parent_dir_entries.children.empty())
File::DeleteDir(ticket_parent_dir);
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);
} }