mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-25 17:00:36 -09:00
GP-871 Ext4 sparse files
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.bin;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import ghidra.formats.gfilesystem.FSRL;
|
||||
|
||||
/**
|
||||
* A {@link ByteProvider} that is a concatenation of sub-ranges of another ByteProvider, also
|
||||
* allowing for non-initialized (sparse) regions.
|
||||
* <p>
|
||||
* Not thread-safe when extents are being added.
|
||||
*/
|
||||
public class ExtentsByteProvider implements ByteProvider {
|
||||
|
||||
private ByteProvider delegate;
|
||||
/**
|
||||
* TreeMap of this-provider offsets to the delegate-provider's offsets.
|
||||
* <p>
|
||||
* Each extent/region in the delegate provider is defined by the gap between
|
||||
* adjacent offsetMap entries. The last entry is bounded by the total
|
||||
* length of the provider as specified by the length field.
|
||||
*/
|
||||
private TreeMap<Long, Long> offsetMap = new TreeMap<>(); // this-provider offset -> delegate-provider offset
|
||||
private long length;
|
||||
private FSRL fsrl;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtentsByteProvider}.
|
||||
*
|
||||
* @param provider {@link ByteProvider} to wrap
|
||||
* @param fsrl {@link FSRL} of the byte provider
|
||||
*/
|
||||
public ExtentsByteProvider(ByteProvider provider, FSRL fsrl) {
|
||||
this.delegate = provider;
|
||||
this.fsrl = fsrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extent to the current end of this instance.
|
||||
*
|
||||
* @param offset long byte offset in the delegate ByteProvider
|
||||
* @param extentLen long length of the extent region in the delegate ByteProvider
|
||||
*/
|
||||
public void addExtent(long offset, long extentLen) {
|
||||
if (extentLen <= 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
offsetMap.put(length, offset);
|
||||
length += extentLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a sparse extent to the current end of this instance.
|
||||
*
|
||||
* @param extentLen long length of the sparse extent region
|
||||
*/
|
||||
public void addSparseExtent(long extentLen) {
|
||||
addExtent(-1, extentLen);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FSRL getFSRL() {
|
||||
return fsrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAbsolutePath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() throws IOException {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidIndex(long index) {
|
||||
return 0 <= index && index < length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte(long index) throws IOException {
|
||||
ensureBounds(index, 1);
|
||||
|
||||
Entry<Long, Long> entry = offsetMap.floorEntry(index);
|
||||
long extentStart = entry.getKey();
|
||||
long extentOffset = index - extentStart;
|
||||
long delegateExtentStart = entry.getValue();
|
||||
|
||||
return (delegateExtentStart != -1)
|
||||
? delegate.readByte(delegateExtentStart + extentOffset)
|
||||
: 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readBytes(long index, long longCount) throws IOException {
|
||||
if (longCount >= Integer.MAX_VALUE) {
|
||||
throw new IOException("Unable to read " + longCount + " bytes at once");
|
||||
}
|
||||
ensureBounds(index, longCount);
|
||||
|
||||
int count = (int) longCount;
|
||||
byte[] result = new byte[count];
|
||||
int bytesRead = 0;
|
||||
while (bytesRead < count) {
|
||||
long offsetToRead = index + bytesRead;
|
||||
Entry<Long, Long> entry = offsetMap.floorEntry(offsetToRead);
|
||||
Entry<Long, Long> nextEntry = offsetMap.higherEntry(entry.getKey());
|
||||
|
||||
long extentStart = entry.getKey();
|
||||
long extentOffset = offsetToRead - extentStart;
|
||||
long extentEnd = (nextEntry != null) ? nextEntry.getKey() : length;
|
||||
long delegateExtentStart = entry.getValue();
|
||||
int bytesToRead =
|
||||
(int) Math.min(count - bytesRead, extentEnd - extentStart - extentOffset);
|
||||
if (delegateExtentStart != -1) {
|
||||
long delegateOffsetToRead = delegateExtentStart + extentOffset;
|
||||
byte[] extentBytes = delegate.readBytes(delegateOffsetToRead, bytesToRead);
|
||||
System.arraycopy(extentBytes, 0, result, bytesRead, bytesToRead);
|
||||
}
|
||||
else {
|
||||
// the extent was not present, result will be 0's
|
||||
}
|
||||
bytesRead += bytesToRead;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream(long index) throws IOException {
|
||||
return new ByteProviderInputStream(this, 0, length);
|
||||
}
|
||||
|
||||
private void ensureBounds(long index, long count) throws IOException {
|
||||
if (index < 0 || index > length) {
|
||||
throw new IOException("Invalid index: " + index);
|
||||
}
|
||||
if (index + count > length) {
|
||||
throw new IOException("Unable to read past EOF: " + index + ", " + count);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.bin;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ExtentsByteProviderTest {
|
||||
private ByteArrayProvider bap(int... values) {
|
||||
byte[] bytes = new byte[values.length];
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
bytes[i] = (byte) values[i];
|
||||
}
|
||||
return new ByteArrayProvider(bytes);
|
||||
}
|
||||
|
||||
/*
|
||||
* "NN 01 NN 03 NN 05 NN 07 NN 09"... (NN = blockNumber, 00-FF = offset in block)
|
||||
*/
|
||||
private ByteArrayProvider patternedBAP(int bs, int count) {
|
||||
byte[] bytes = new byte[bs * count];
|
||||
for (int blockNum = 0; blockNum < count; blockNum++) {
|
||||
int blockStart = blockNum * bs;
|
||||
Arrays.fill(bytes, blockStart, blockStart + bs, (byte) blockNum);
|
||||
for (int i = 1; i < bs; i += 2) {
|
||||
bytes[i + blockStart] = (byte) (i % 256);
|
||||
}
|
||||
}
|
||||
return new ByteArrayProvider(bytes);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testEmptyExtentBP() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(bap(55), null)) {
|
||||
ebp.readByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtentBP_SingleByteRead() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(10, 10);
|
||||
ebp.addExtent(0, 1);
|
||||
ebp.addExtent(0, 10);
|
||||
|
||||
assertEquals(21, ebp.length());
|
||||
|
||||
assertEquals(0x01, ebp.readByte(0));
|
||||
assertEquals(0x01, ebp.readByte(1));
|
||||
assertEquals(0x01, ebp.readByte(2));
|
||||
assertEquals(0x03, ebp.readByte(3));
|
||||
assertEquals(0x09, ebp.readByte(9));
|
||||
|
||||
assertEquals(0x00, ebp.readByte(11));
|
||||
assertEquals(0x01, ebp.readByte(12));
|
||||
assertEquals(0x00, ebp.readByte(13));
|
||||
assertEquals(0x03, ebp.readByte(14));
|
||||
assertEquals(0x09, ebp.readByte(20));
|
||||
|
||||
try {
|
||||
ebp.readByte(21);
|
||||
fail();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtentBP_MultiByteRead() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(10, 10);
|
||||
ebp.addExtent(0, 1);
|
||||
ebp.addExtent(0, 10);
|
||||
|
||||
assertEquals(21, ebp.length());
|
||||
|
||||
byte[] bytes = ebp.readBytes(0, 21);
|
||||
assertEquals(0x01, bytes[0]);
|
||||
assertEquals(0x01, bytes[1]);
|
||||
assertEquals(0x01, bytes[2]);
|
||||
assertEquals(0x03, bytes[3]);
|
||||
assertEquals(0x09, bytes[9]);
|
||||
|
||||
assertEquals(0x00, bytes[11]);
|
||||
assertEquals(0x01, bytes[12]);
|
||||
assertEquals(0x00, bytes[13]);
|
||||
assertEquals(0x03, bytes[14]);
|
||||
assertEquals(0x09, bytes[20]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtentBP_MisalignedMultiByteRead() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(10, 10);
|
||||
ebp.addExtent(0, 10);
|
||||
|
||||
assertEquals(20, ebp.length());
|
||||
|
||||
byte[] bytes = ebp.readBytes(5, 10);
|
||||
assertEquals(0x05, bytes[0]);
|
||||
assertEquals(0x01, bytes[1]);
|
||||
assertEquals(0x07, bytes[2]);
|
||||
assertEquals(0x01, bytes[3]);
|
||||
assertEquals(0x09, bytes[4]);
|
||||
assertEquals(0x00, bytes[5]);
|
||||
assertEquals(0x01, bytes[6]);
|
||||
assertEquals(0x00, bytes[7]);
|
||||
assertEquals(0x03, bytes[8]);
|
||||
assertEquals(0x00, bytes[9]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallExtentBP() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(10, 1);
|
||||
ebp.addExtent(0, 1);
|
||||
|
||||
assertEquals(2, ebp.length());
|
||||
|
||||
assertEquals(0x01, ebp.readByte(0));
|
||||
assertEquals(0x00, ebp.readByte(1));
|
||||
|
||||
try {
|
||||
ebp.readByte(3);
|
||||
fail();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtentBP_SparseMultiByteRead() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(-1, 5);
|
||||
|
||||
assertEquals(5, ebp.length());
|
||||
|
||||
byte[] bytes = ebp.readBytes(0, 5);
|
||||
assertEquals(0x00, bytes[0]);
|
||||
assertEquals(0x00, bytes[1]);
|
||||
assertEquals(0x00, bytes[2]);
|
||||
assertEquals(0x00, bytes[3]);
|
||||
assertEquals(0x00, bytes[4]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtentBP_SparseSingleByteRead() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(-1, 5);
|
||||
|
||||
assertEquals(5, ebp.length());
|
||||
|
||||
assertEquals(0x00, ebp.readByte(0));
|
||||
assertEquals(0x00, ebp.readByte(1));
|
||||
assertEquals(0x00, ebp.readByte(2));
|
||||
assertEquals(0x00, ebp.readByte(3));
|
||||
assertEquals(0x00, ebp.readByte(4));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtentBP_MixedSparseMultiByteRead() throws IOException {
|
||||
try (ExtentsByteProvider ebp = new ExtentsByteProvider(patternedBAP(10, 10), null)) {
|
||||
ebp.addExtent(10, 10);
|
||||
ebp.addExtent(-1, 5);
|
||||
ebp.addExtent(0, 10);
|
||||
|
||||
assertEquals(25, ebp.length());
|
||||
|
||||
byte[] bytes = ebp.readBytes(0, 25);
|
||||
assertEquals(0x01, bytes[0]);
|
||||
assertEquals(0x01, bytes[1]);
|
||||
assertEquals(0x01, bytes[2]);
|
||||
assertEquals(0x03, bytes[3]);
|
||||
assertEquals(0x09, bytes[9]);
|
||||
|
||||
assertEquals(0x00, bytes[10]);
|
||||
assertEquals(0x00, bytes[11]);
|
||||
assertEquals(0x00, bytes[12]);
|
||||
assertEquals(0x00, bytes[13]);
|
||||
assertEquals(0x00, bytes[14]);
|
||||
|
||||
assertEquals(0x00, bytes[15]);
|
||||
assertEquals(0x01, bytes[16]);
|
||||
assertEquals(0x00, bytes[17]);
|
||||
assertEquals(0x03, bytes[18]);
|
||||
assertEquals(0x09, bytes[24]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,10 +198,7 @@ public class Ext4Analyzer extends FileFormatAnalyzer {
|
||||
List<Ext4Extent> entries = i_block.getExtentEntries();
|
||||
for( int i = 0; i < numEntries; i++ ) {
|
||||
Ext4Extent extent = entries.get(i);
|
||||
int low = extent.getEe_start_lo();
|
||||
int high = extent.getEe_start_hi();
|
||||
int blockNumber = (high << 16) | low;
|
||||
long offset = blockNumber * blockSize;
|
||||
long offset = extent.getExtentStartBlockNumber() * blockSize;
|
||||
reader.setPointerIndex(offset);
|
||||
Address address = toAddr(program, offset);
|
||||
if( isDirEntry2 ) {
|
||||
|
||||
@@ -15,16 +15,12 @@
|
||||
*/
|
||||
package ghidra.file.formats.ext4;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.Structure;
|
||||
import ghidra.program.model.data.StructureDataType;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class Ext4Extent implements StructConverter {
|
||||
|
||||
private int ee_block;
|
||||
@@ -59,6 +55,33 @@ public class Ext4Extent implements StructConverter {
|
||||
return ee_start_lo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of blocks this extent contains.
|
||||
*
|
||||
* @return number of blocks in this extent
|
||||
*/
|
||||
public int getExtentBlockCount() {
|
||||
return Short.toUnsignedInt(ee_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream block number of where this extent starts.
|
||||
*
|
||||
* @return block number (in the constructed stream) of this extent
|
||||
*/
|
||||
public long getStreamBlockNumber() {
|
||||
return Integer.toUnsignedLong(ee_block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block number of where the data for this extent is stored.
|
||||
*
|
||||
* @return starting block number of where data for this extent is stored
|
||||
*/
|
||||
public long getExtentStartBlockNumber() {
|
||||
return Short.toUnsignedLong(ee_start_hi) << 32 | Integer.toUnsignedLong(ee_start_lo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
Structure structure = new StructureDataType("ext4_extent", 0);
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
package ghidra.file.formats.ext4;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.formats.gfilesystem.*;
|
||||
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
|
||||
import ghidra.util.NumericUtilities;
|
||||
@@ -113,8 +113,8 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
if (parent == null) {
|
||||
parent = fsih.getRootDir();
|
||||
}
|
||||
parent = fsih.storeFileWithParent(name, parent, -1, true,
|
||||
(inode.getI_size_high() << 32) | inode.getI_size_lo(), new Ext4File(name, inode));
|
||||
parent = fsih.storeFileWithParent(name, parent, -1, true, inode.getSize(),
|
||||
new Ext4File(name, inode));
|
||||
}
|
||||
if ((inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) == 0) {
|
||||
return;
|
||||
@@ -142,10 +142,7 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
monitor.checkCanceled();
|
||||
Ext4Extent extent = entries.get(i);
|
||||
long low = extent.getEe_start_lo() & 0xffffffffL;
|
||||
long high = extent.getEe_start_hi() & 0xffffffffL;
|
||||
long blockNumber = (high << 16) | low;
|
||||
long offset = blockNumber * blockSize;
|
||||
long offset = extent.getExtentStartBlockNumber() * blockSize;
|
||||
reader.setPointerIndex(offset);
|
||||
if (isDirEntry2) {
|
||||
processDirEntry2(reader, superBlock, inodes, parent, monitor, extent, offset);
|
||||
@@ -241,10 +238,9 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
private void storeFile(Ext4Inode[] inodes, Ext4DirEntry dirEnt, GFile parent) {
|
||||
int fileInodeNum = dirEnt.getInode();
|
||||
Ext4Inode fileInode = inodes[fileInodeNum];
|
||||
long fileSize = (fileInode.getI_size_high() << 32) | fileInode.getI_size_lo();
|
||||
fsih.storeFileWithParent(dirEnt.getName(), parent, -1,
|
||||
(fileInode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR, fileSize,
|
||||
new Ext4File(dirEnt.getName(), fileInode));
|
||||
(fileInode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR,
|
||||
fileInode.getSize(), new Ext4File(dirEnt.getName(), fileInode));
|
||||
inodes[fileInodeNum] = null;
|
||||
}
|
||||
|
||||
@@ -254,9 +250,8 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
if (fileInode == null) {
|
||||
return;//TODO
|
||||
}
|
||||
long fileSize = (fileInode.getI_size_high() << 32) | fileInode.getI_size_lo();
|
||||
fsih.storeFileWithParent(dirEnt2.getName(), parent, -1,
|
||||
dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_DIRECTORY, fileSize,
|
||||
dirEnt2.getFile_type() == Ext4Constants.FILE_TYPE_DIRECTORY, fileInode.getSize(),
|
||||
new Ext4File(dirEnt2.getName(), fileInode));
|
||||
inodes[fileInodeNum] = null;
|
||||
}
|
||||
@@ -284,7 +279,7 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
}
|
||||
Ext4Inode inode = ext4File.getInode();
|
||||
String info = "";
|
||||
long size = (inode.getI_size_high() << 32) | inode.getI_size_lo();
|
||||
long size = inode.getSize();
|
||||
if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) {
|
||||
Ext4IBlock block = inode.getI_block();
|
||||
byte[] extra = block.getExtra();
|
||||
@@ -299,27 +294,8 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
@Override
|
||||
public InputStream getInputStream(GFile file, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
Ext4File extFile = fsih.getMetadata(file);
|
||||
if (extFile == null) {
|
||||
return null;
|
||||
}
|
||||
Ext4Inode inode = extFile.getInode();
|
||||
if (inode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) {
|
||||
throw new IOException(extFile.getName() + " is a directory.");
|
||||
}
|
||||
|
||||
if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) {
|
||||
inode = resolveSymLink(file);
|
||||
if (inode == null) {
|
||||
throw new IOException(extFile.getName() + " is a broken symlink.");
|
||||
}
|
||||
}
|
||||
|
||||
return getInputStream(inode);
|
||||
ByteProvider bp = getByteProvider(file, monitor);
|
||||
return (bp != null) ? new ByteProviderInputStream(bp, 0, bp.length()) : null;
|
||||
}
|
||||
|
||||
private static final int MAX_SYMLINK_LOOKUP_COUNT = 100;
|
||||
@@ -351,57 +327,76 @@ public class Ext4FileSystem implements GFileSystem {
|
||||
return null;
|
||||
}
|
||||
|
||||
private InputStream getInputStream(Ext4Inode inode) throws IOException {
|
||||
int i_size_lo = inode.getI_size_lo();
|
||||
int i_size_high = inode.getI_size_high();
|
||||
long size = (i_size_high << 32) | i_size_lo;
|
||||
private Ext4Inode getInodeFor(GFile file) throws IOException {
|
||||
Ext4File extFile = fsih.getMetadata(file);
|
||||
if (extFile == null) {
|
||||
return null;
|
||||
}
|
||||
Ext4Inode inode = extFile.getInode();
|
||||
if (inode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean usesExtents = (inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0;
|
||||
if (usesExtents) {
|
||||
Ext4IBlock i_block = inode.getI_block();
|
||||
Ext4ExtentHeader header = i_block.getHeader();
|
||||
if (header.getEh_depth() == 0) {
|
||||
List<Ext4Extent> entries = i_block.getExtentEntries();
|
||||
return concatenateExtents(entries, size);
|
||||
if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFLNK) {
|
||||
inode = resolveSymLink(file);
|
||||
if (inode == null) {
|
||||
throw new IOException(extFile.getName() + " is a broken symlink.");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return inode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The file is spread throughout the ext4 file, so
|
||||
* concatenate each extent into one contiguous stream.
|
||||
* Returns a {@link ByteProvider} that supplies the bytes of the requested file.
|
||||
*
|
||||
* TODO better memory management? currently loads entire file into memory.
|
||||
* @param file {@link GFile} to get
|
||||
* @param monitor {@link TaskMonitor} to cancel
|
||||
* @return {@link ByteProvider} containing the bytes of the requested file, caller is
|
||||
* responsible for closing the ByteProvider
|
||||
* @throws IOException if error
|
||||
*/
|
||||
private InputStream concatenateExtents(List<Ext4Extent> entries, long actualSize)
|
||||
throws IOException {
|
||||
if (actualSize > Integer.MAX_VALUE) {
|
||||
throw new IOException(
|
||||
"File is >2GB, too large to extract. Please report to Ghidra team.");
|
||||
public ByteProvider getByteProvider(GFile file, TaskMonitor monitor) throws IOException {
|
||||
Ext4Inode inode = getInodeFor(file);
|
||||
if (inode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
for (int i = 0; i < entries.size(); ++i) {
|
||||
Ext4Extent extent = entries.get(i);
|
||||
|
||||
long low = extent.getEe_start_lo() & 0xffffffffL;
|
||||
long high = extent.getEe_start_hi() & 0xffffffffL;
|
||||
long blockNumber = (high << 16) | low;
|
||||
long extentOffset = blockNumber * blockSize;
|
||||
long extentSize = (extent.getEe_len() & 0xffffL) * blockSize;
|
||||
|
||||
try {
|
||||
byte[] extentBytes = provider.readBytes(extentOffset, extentSize);
|
||||
baos.write(extentBytes);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
if ((inode.getI_mode() & Ext4Constants.I_MODE_MASK) == Ext4Constants.S_IFDIR) {
|
||||
throw new IOException(file.getName() + " is a directory.");
|
||||
}
|
||||
|
||||
return new ByteArrayInputStream(baos.toByteArray(), 0, (int) actualSize);
|
||||
boolean usesExtents = (inode.getI_flags() & Ext4Constants.EXT4_EXTENTS_FL) != 0;
|
||||
if (!usesExtents) {
|
||||
throw new IOException("Unsupported file storage: not EXT4_EXTENTS: " + file.getPath());
|
||||
}
|
||||
|
||||
Ext4IBlock i_block = inode.getI_block();
|
||||
Ext4ExtentHeader header = i_block.getHeader();
|
||||
if (header.getEh_depth() != 0) {
|
||||
throw new IOException("Unsupported file storage: eh_depth: " + file.getPath());
|
||||
}
|
||||
|
||||
long fileSize = inode.getSize();
|
||||
ExtentsByteProvider ebp = new ExtentsByteProvider(provider, file.getFSRL());
|
||||
for (Ext4Extent extent : i_block.getExtentEntries()) {
|
||||
long startPos = extent.getStreamBlockNumber() * blockSize;
|
||||
long providerOfs = extent.getExtentStartBlockNumber() * blockSize;
|
||||
long extentLen = extent.getExtentBlockCount() * blockSize;
|
||||
if (ebp.length() < startPos) {
|
||||
ebp.addSparseExtent(startPos - ebp.length());
|
||||
}
|
||||
if (ebp.length() + extentLen > fileSize) {
|
||||
// the last extent may have a trailing partial block
|
||||
extentLen = fileSize - ebp.length();
|
||||
}
|
||||
|
||||
ebp.addExtent(providerOfs, extentLen);
|
||||
}
|
||||
if (ebp.length() < fileSize) {
|
||||
// trailing sparse. not sure if possible.
|
||||
ebp.addSparseExtent(fileSize - ebp.length());
|
||||
}
|
||||
return ebp;
|
||||
}
|
||||
|
||||
private Ext4Inode[] getInodes(BinaryReader reader, Ext4SuperBlock superBlock,
|
||||
|
||||
@@ -15,17 +15,12 @@
|
||||
*/
|
||||
package ghidra.file.formats.ext4;
|
||||
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.StructConverter;
|
||||
import ghidra.program.model.data.ArrayDataType;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.data.Structure;
|
||||
import ghidra.program.model.data.StructureDataType;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
public class Ext4Inode implements StructConverter {
|
||||
|
||||
private short i_mode;
|
||||
@@ -199,6 +194,15 @@ public class Ext4Inode implements StructConverter {
|
||||
return i_projid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of this file.
|
||||
*
|
||||
* @return size of this file
|
||||
*/
|
||||
public long getSize() {
|
||||
return Integer.toUnsignedLong(i_size_high) << 32 | Integer.toUnsignedLong(i_size_lo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataType toDataType() throws DuplicateNameException, IOException {
|
||||
DataType iBlockDataType = i_block.toDataType();
|
||||
|
||||
@@ -21,17 +21,13 @@ import java.util.List;
|
||||
import ghidra.app.cmd.comments.SetCommentCmd;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.util.bin.BinaryReader;
|
||||
import ghidra.app.util.bin.ByteProvider;
|
||||
import ghidra.app.util.bin.MemoryByteProvider;
|
||||
import ghidra.app.util.bin.*;
|
||||
import ghidra.app.util.importer.MessageLog;
|
||||
import ghidra.file.analyzers.FileFormatAnalyzer;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.data.DataType;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -260,10 +256,7 @@ public class NewExt4Analyzer extends FileFormatAnalyzer {
|
||||
if ( iBlock != null ) {
|
||||
for ( Ext4Extent extent : iBlock.getExtentEntries( ) ) {
|
||||
monitor.checkCanceled( );
|
||||
long lo = extent.getEe_start_lo( ) & 0xffffffffL;
|
||||
long hi = extent.getEe_start_hi( ) & 0xffffffffL;
|
||||
long value = ( hi << 32 ) | lo;
|
||||
long destination = value * blockSize;
|
||||
long destination = extent.getExtentStartBlockNumber() * blockSize;
|
||||
comment += "Extent: 0x" + Long.toHexString( destination ) + "\n";
|
||||
}
|
||||
}
|
||||
@@ -346,10 +339,7 @@ public class NewExt4Analyzer extends FileFormatAnalyzer {
|
||||
List<Ext4Extent> entries = i_block.getExtentEntries();
|
||||
for ( int i = 0; i < numEntries; i++ ) {
|
||||
Ext4Extent extent = entries.get( i );
|
||||
long lo = extent.getEe_start_lo( ) & 0xffffffffL;
|
||||
long hi = extent.getEe_start_hi( ) & 0xffffffffL;
|
||||
long value = ( hi << 32 ) | lo;
|
||||
long offset = value * blockSize;
|
||||
long offset = extent.getExtentStartBlockNumber() * blockSize;
|
||||
reader.setPointerIndex(offset);
|
||||
Address address = toAddr( program, offset );
|
||||
if ( isDirEntry2 ) {
|
||||
|
||||
Reference in New Issue
Block a user