[PATCH v2] erofs-rs: fix inline xattr size for tail offsets

[email protected]
Newsgroups org.ozlabs.lists.linux-erofs
Message-ID <[email protected]>
From: Transcendental-Programmer <[email protected]>

---
 erofs/src/async/filesystem.rs | 64 ++++++++++++++++++++++++++++++++++-
 erofs/src/filesystem.rs       | 53 +++++++++++++++++++++++++++--
 erofs/src/sync/filesystem.rs  | 34 ++++++++++++++++++-
 erofs/src/types.rs            |  7 ++++
 4 files changed, 153 insertions(+), 5 deletions(-)

diff --git a/erofs/src/async/filesystem.rs b/erofs/src/async/filesystem.rs
index 1987f09..c791b49 100644
--- a/erofs/src/async/filesystem.rs
+++ b/erofs/src/async/filesystem.rs
@@ -1,5 +1,7 @@
 use alloc::format;
 use alloc::vec::Vec;
+use binrw::{BinRead, io::Cursor};
+use core::mem::size_of;
 use typed_path::Component;
 
 use bytes::Buf;
@@ -83,6 +85,44 @@ impl<I: AsyncImage> EroFS<I> {
         self.core.block_size
     }
 
+    async fn xattr_ibody_size(&self, inode: &Inode) -> Result<usize> {
+        let total_count = inode.xattr_count();
+        if total_count == 0 {
+            return Ok(0);
+        }
+
+        let inode_offset = self.core.get_inode_offset(inode.id()) as usize;
+        let xattr_start = inode_offset + inode.size();
+
+        let mut header_buf = vec![0u8; size_of::<XattrHeader>()];
+        self.image.read_exact_at(&mut header_buf, xattr_start).await?;
+        let header = XattrHeader::read(&mut Cursor::new(&header_buf))?;
+
+        let shared_count = header.shared_count as usize;
+        let total = total_count as usize;
+        if shared_count > total {
+            return Err(Error::CorruptedData(
+                "xattr shared count exceeds total count".to_string(),
+            ));
+        }
+
+        let mut offset = xattr_start + size_of::<XattrHeader>() + shared_count * size_of::<u32>();
+        let inline_count = total - shared_count;
+        for _ in 0..inline_count {
+            let mut entry_buf = vec![0u8; size_of::<XattrEntry>()];
+            self.image.read_exact_at(&mut entry_buf, offset).await?;
+            let entry = XattrEntry::read(&mut Cursor::new(&entry_buf))?;
+            offset += size_of::<XattrEntry>();
+
+            let payload = entry.name_len as usize + entry.value_len as usize;
+            offset = offset
+                .checked_add(payload)
+                .ok_or_else(|| Error::CorruptedData("xattr size overflow".to_string()))?;
+        }
+
+        Ok(offset - xattr_start)
+    }
+
     pub async fn get_inode(&self, nid: u64) -> Result<Inode> {
         let offset = self.core.get_inode_offset(nid) as usize;
         let mut buf = vec![0u8; InodeExtended::size()];
@@ -91,7 +131,29 @@ impl<I: AsyncImage> EroFS<I> {
     }
 
     pub(crate) async fn read_inode_block(&self, inode: &Inode, offset: usize) -> Result<Vec<u8>> {
-        match self.core.plan_inode_block_read(inode, offset)? {
+        let layout = inode.layout()?;
+        let xattr_size = if inode.xattr_count() == 0 {
+            0
+        } else {
+            match layout {
+                Layout::FlatInline => {
+                    let block_count = inode.data_size().div_ceil(self.core.block_size);
+                    let block_index = offset / self.core.block_size;
+                    if block_count != 0 && block_index == block_count - 1 {
+                        self.xattr_ibody_size(inode).await?
+                    } else {
+                        0
+                    }
+                }
+                Layout::ChunkBased => self.xattr_ibody_size(inode).await?,
+                _ => 0,
+            }
+        };
+
+        match self
+            .core
+            .plan_inode_block_read(inode, offset, xattr_size)?
+        {
             BlockPlan::Direct { offset, size } => {
                 if size > self.core.block_size {
                     return Err(Error::CorruptedData(format!(
diff --git a/erofs/src/filesystem.rs b/erofs/src/filesystem.rs
index dfe22aa..d44a5cd 100644
--- a/erofs/src/filesystem.rs
+++ b/erofs/src/filesystem.rs
@@ -1,4 +1,5 @@
 use alloc::{format, string::ToString};
+use core::mem::size_of;
 
 use binrw::BinRead;
 use binrw::BinReaderExt;
@@ -87,7 +88,12 @@ impl EroFSCore {
     /// Returns a `BlockPlan` describing what bytes to read.
     /// For `BlockPlan::Chunked`, the caller must perform an additional
     /// read and call `resolve_chunk_read()`.
-    pub(crate) fn plan_inode_block_read(&self, inode: &Inode, offset: usize) -> Result<BlockPlan> {
+    pub(crate) fn plan_inode_block_read(
+        &self,
+        inode: &Inode,
+        offset: usize,
+        xattr_size: usize,
+    ) -> Result<BlockPlan> {
         match inode.layout()? {
             Layout::FlatPlain => {
                 let block_count = inode.data_size().div_ceil(self.block_size);
@@ -112,7 +118,7 @@ impl EroFSCore {
                     // tail block
                     let inode_offset = self.get_inode_offset(inode.id());
                     let buf_size = inode.data_size() % self.block_size;
-                    let offset = inode_offset as usize + inode.size() + inode.xattr_size();
+                    let offset = inode_offset as usize + inode.size() + xattr_size;
                     return Ok(BlockPlan::Direct {
                         offset,
                         size: buf_size,
@@ -151,7 +157,7 @@ impl EroFSCore {
 
                 let inode_offset = self.get_inode_offset(inode.id());
                 let addr_offset =
-                    inode_offset as usize + inode.size() + inode.xattr_size() + (chunk_index * 4);
+                    inode_offset as usize + inode.size() + xattr_size + (chunk_index * 4);
 
                 Ok(BlockPlan::Chunked {
                     addr_offset,
@@ -201,4 +207,45 @@ impl EroFSCore {
     pub(crate) fn block_offset(&self, block: u32) -> u64 {
         (block as u64) << self.super_block.blk_size_bits
     }
+
+    pub(crate) fn xattr_ibody_size_from_slice(
+        data: &[u8],
+        total_count: u16,
+    ) -> Result<usize> {
+        if total_count == 0 {
+            return Ok(0);
+        }
+
+        let header = XattrHeader::read(&mut Cursor::new(data))?;
+        let shared_count = header.shared_count as usize;
+        let total = total_count as usize;
+        if shared_count > total {
+            return Err(Error::CorruptedData(
+                "xattr shared count exceeds total count".to_string(),
+            ));
+        }
+
+        let mut offset = size_of::<XattrHeader>() + shared_count * size_of::<u32>();
+        let inline_count = total - shared_count;
+        for _ in 0..inline_count {
+            if data.len() < offset + size_of::<XattrEntry>() {
+                return Err(Error::CorruptedData(
+                    "xattr entry header out of range".to_string(),
+                ));
+            }
+
+            let entry = XattrEntry::read(&mut Cursor::new(&data[offset..]))?;
+            offset += size_of::<XattrEntry>();
+
+            let payload = entry.name_len as usize + entry.value_len as usize;
+            if data.len() < offset + payload {
+                return Err(Error::CorruptedData(
+                    "xattr entry payload out of range".to_string(),
+                ));
+            }
+            offset += payload;
+        }
+
+        Ok(offset)
+    }
 }
diff --git a/erofs/src/sync/filesystem.rs b/erofs/src/sync/filesystem.rs
index 2727383..7d94727 100644
--- a/erofs/src/sync/filesystem.rs
+++ b/erofs/src/sync/filesystem.rs
@@ -143,6 +143,16 @@ impl<I: Image> EroFS<I> {
         self.core.block_size
     }
 
+    fn xattr_ibody_size(&self, inode: &Inode) -> Result<usize> {
+        let inode_offset = self.core.get_inode_offset(inode.id()) as usize;
+        let xattr_start = inode_offset + inode.size();
+        let data = self
+            .image
+            .get(xattr_start..)
+            .ok_or_else(|| Error::OutOfBounds("failed to read xattr data".to_string()))?;
+        EroFSCore::xattr_ibody_size_from_slice(data, inode.xattr_count())
+    }
+
     pub fn get_inode(&self, nid: u64) -> Result<Inode> {
         let offset = self.core.get_inode_offset(nid) as usize;
         let data = self
@@ -153,7 +163,29 @@ impl<I: Image> EroFS<I> {
     }
 
     pub(crate) fn get_inode_block(&self, inode: &Inode, offset: usize) -> Result<&[u8]> {
-        match self.core.plan_inode_block_read(inode, offset)? {
+        let layout = inode.layout()?;
+        let xattr_size = if inode.xattr_count() == 0 {
+            0
+        } else {
+            match layout {
+                Layout::FlatInline => {
+                    let block_count = inode.data_size().div_ceil(self.core.block_size);
+                    let block_index = offset / self.core.block_size;
+                    if block_count != 0 && block_index == block_count - 1 {
+                        self.xattr_ibody_size(inode)?
+                    } else {
+                        0
+                    }
+                }
+                Layout::ChunkBased => self.xattr_ibody_size(inode)?,
+                _ => 0,
+            }
+        };
+
+        match self
+            .core
+            .plan_inode_block_read(inode, offset, xattr_size)?
+        {
             BlockPlan::Direct { offset, size } => self
                 .image
                 .get(offset..offset + size)
diff --git a/erofs/src/types.rs b/erofs/src/types.rs
index f4cfcf6..5bb6df1 100644
--- a/erofs/src/types.rs
+++ b/erofs/src/types.rs
@@ -185,6 +185,13 @@ impl Inode {
         }
     }
 
+    pub fn xattr_count(&self) -> u16 {
+        match self {
+            Self::Compact((_, n)) => n.xattr_count,
+            Self::Extended((_, n)) => n.xattr_count,
+        }
+    }
+
     pub fn file_type(&self) -> FileType {
         match self {
             Self::Compact((_, n)) => FileType::from_raw_mode(n.mode as _),
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.