CloverBootloader/Ext4Pkg/Ext4Dxe/Symlink.c

262 lines
7.5 KiB
C

/** @file
Symbolic links routines
Copyright (c) 2022-2023 Savva Mitrofanov All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Ext4Dxe.h"
/**
Detects if a symlink is a fast symlink.
@param[in] File Pointer to the opened file.
@return BOOLEAN Whether symlink is a fast symlink
**/
STATIC
BOOLEAN
Ext4SymlinkIsFastSymlink (
IN CONST EXT4_FILE *File
)
{
//
// Detection logic of the fast-symlink splits into two behaviors - old and new.
// The old behavior is based on comparing the extended attribute blocks
// with the inode's i_blocks, and if it's zero we know the inode isn't storing
// the link in filesystem blocks, so we look to the inode->i_data.
// The new behavior is apparently needed only with the large EA inode feature.
// In this case we check that inode size less than maximum fast symlink size.
// So, we revert to the old behavior if the large EA inode feature is not set.
//
UINT32 FileAcl;
UINT32 ExtAttrBlocks;
if ((File->Inode->i_flags & EXT4_EA_INODE_FL) == 0) {
FileAcl = File->Inode->i_file_acl;
if (EXT4_IS_64_BIT (File->Partition)) {
//
// We don't care about final value, we are just checking for any bit is set
// so, thats why we neglect LShiftU64(.., 32)
//
FileAcl |= File->Inode->i_osd2.data_linux.l_i_file_acl_high;
}
ExtAttrBlocks = FileAcl != 0 ? (File->Partition->BlockSize >> 9) : 0;
return File->Inode->i_blocks == ExtAttrBlocks;
}
return EXT4_INODE_SIZE (File->Inode) <= EXT4_FAST_SYMLINK_MAX_SIZE;
}
/**
Reads a fast symlink file.
@param[in] Partition Pointer to the ext4 partition.
@param[in] File Pointer to the open symlink file.
@param[out] AsciiSymlink Pointer to the output ascii symlink string.
@param[out] AsciiSymlinkSize Pointer to the output ascii symlink string length.
@retval EFI_SUCCESS Fast symlink was read.
@retval EFI_OUT_OF_RESOURCES Memory allocation error.
**/
STATIC
EFI_STATUS
Ext4ReadFastSymlink (
IN EXT4_PARTITION *Partition,
IN EXT4_FILE *File,
OUT CHAR8 **AsciiSymlink,
OUT UINT32 *AsciiSymlinkSize
)
{
UINT32 SymlinkSize;
CHAR8 *AsciiSymlinkTmp;
//
// Fast-symlink's EXT4_INODE_SIZE is not necessarily validated when we checked it in
// Ext4SymlinkIsFastSymlink(), so truncate if necessary.
//
SymlinkSize = (UINT32)MIN (EXT4_INODE_SIZE (File->Inode), EXT4_FAST_SYMLINK_MAX_SIZE);
AsciiSymlinkTmp = AllocatePool (SymlinkSize + 1);
if (AsciiSymlinkTmp == NULL) {
DEBUG ((DEBUG_ERROR, "[ext4] Failed to allocate symlink ascii string buffer\n"));
return EFI_OUT_OF_RESOURCES;
}
CopyMem (AsciiSymlinkTmp, File->Inode->i_data, SymlinkSize);
//
// Add null-terminator
//
AsciiSymlinkTmp[SymlinkSize] = '\0';
*AsciiSymlink = AsciiSymlinkTmp;
*AsciiSymlinkSize = SymlinkSize + 1;
return EFI_SUCCESS;
}
/**
Reads a slow symlink file.
@param[in] Partition Pointer to the ext4 partition.
@param[in] File Pointer to the open symlink file.
@param[out] AsciiSymlink Pointer to the output ascii symlink string.
@param[out] AsciiSymlinkSize Pointer to the output ascii symlink string length.
@retval EFI_SUCCESS Slow symlink was read.
@retval EFI_OUT_OF_RESOURCES Memory allocation error.
@retval EFI_INVALID_PARAMETER Slow symlink path has incorrect length
@retval EFI_VOLUME_CORRUPTED Symlink read block size differ from inode value
**/
STATIC
EFI_STATUS
Ext4ReadSlowSymlink (
IN EXT4_PARTITION *Partition,
IN EXT4_FILE *File,
OUT CHAR8 **AsciiSymlink,
OUT UINT32 *AsciiSymlinkSize
)
{
EFI_STATUS Status;
CHAR8 *SymlinkTmp;
UINT64 SymlinkSizeTmp;
UINT32 SymlinkAllocateSize;
UINTN ReadSize;
SymlinkSizeTmp = EXT4_INODE_SIZE (File->Inode);
//
// Allocate EXT4_INODE_SIZE + 1
//
if (SymlinkSizeTmp >= EXT4_EFI_PATH_MAX) {
DEBUG ((
DEBUG_WARN,
"[ext4] Warn: symlink path maximum length was hit!\n"
));
return EFI_INVALID_PARAMETER;
}
SymlinkAllocateSize = (UINT32)SymlinkSizeTmp + 1;
SymlinkTmp = AllocatePool (SymlinkAllocateSize);
if (SymlinkTmp == NULL) {
DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink ascii string buffer\n"));
return EFI_OUT_OF_RESOURCES;
}
ReadSize = (UINTN)SymlinkSizeTmp;
Status = Ext4Read (Partition, File, SymlinkTmp, File->Position, &ReadSize);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_FS, "[ext4] Failed to read symlink from blocks with status %r\n", Status));
FreePool (SymlinkTmp);
return Status;
}
if (SymlinkSizeTmp != ReadSize) {
DEBUG ((
DEBUG_FS,
"[ext4] Error! The size of the read block doesn't match the value from the inode!\n"
));
FreePool (SymlinkTmp);
return EFI_VOLUME_CORRUPTED;
}
//
// Add null-terminator
//
SymlinkTmp[ReadSize] = '\0';
*AsciiSymlinkSize = SymlinkAllocateSize;
*AsciiSymlink = SymlinkTmp;
return EFI_SUCCESS;
}
/**
Reads a symlink file.
@param[in] Partition Pointer to the ext4 partition.
@param[in] File Pointer to the open symlink file.
@param[out] Symlink Pointer to the output unicode symlink string.
@retval EFI_SUCCESS Symlink was read.
@retval EFI_ACCESS_DENIED Symlink is encrypted.
@retval EFI_OUT_OF_RESOURCES Memory allocation error.
@retval EFI_INVALID_PARAMETER Symlink path has incorrect length
@retval EFI_VOLUME_CORRUPTED Symlink read block size differ from inode value
**/
EFI_STATUS
Ext4ReadSymlink (
IN EXT4_PARTITION *Partition,
IN EXT4_FILE *File,
OUT CHAR16 **Symlink
)
{
EFI_STATUS Status;
CHAR8 *SymlinkTmp;
UINT32 SymlinkSize;
CHAR16 *Symlink16Tmp;
CHAR16 *Needle;
//
// Assume that we already read Inode via Ext4ReadInode
// Skip reading, just check encryption flag
//
if ((File->Inode->i_flags & EXT4_ENCRYPT_FL) != 0) {
DEBUG ((DEBUG_WARN, "[ext4] Warn: symlink is encrypted\n"));
return EFI_ACCESS_DENIED;
}
if (Ext4SymlinkIsFastSymlink (File)) {
Status = Ext4ReadFastSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
} else {
Status = Ext4ReadSlowSymlink (Partition, File, &SymlinkTmp, &SymlinkSize);
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_FS, "[ext4] Symlink read error with Status %r\n", Status));
return Status;
}
Symlink16Tmp = AllocatePool (SymlinkSize * sizeof (CHAR16));
if (Symlink16Tmp == NULL) {
DEBUG ((DEBUG_FS, "[ext4] Failed to allocate symlink unicode string buffer\n"));
FreePool (SymlinkTmp);
return EFI_OUT_OF_RESOURCES;
}
Status = AsciiStrToUnicodeStrS (
SymlinkTmp,
Symlink16Tmp,
SymlinkSize
);
FreePool (SymlinkTmp);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_FS,
"[ext4] Failed to convert ascii symlink to unicode with Status %r\n",
Status
));
FreePool (Symlink16Tmp);
return Status;
}
//
// Convert to UEFI slashes
//
for (Needle = Symlink16Tmp; *Needle != L'\0'; Needle++) {
if (*Needle == L'/') {
*Needle = L'\\';
}
}
*Symlink = Symlink16Tmp;
return Status;
}