/** @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; }