* [GSoC 2026] erofs-rs xattr POC + xattr_size() fix @ 2026-03-31 13:31 Priyansh Saxena 2026-04-09 15:48 ` [PATCH] erofs-rs: fix inline xattr size for tail offsets priyena.programming 0 siblings, 1 reply; 4+ messages in thread From: Priyansh Saxena @ 2026-03-31 13:31 UTC (permalink / raw) To: linux-erofs [-- Attachment #1: Type: text/plain, Size: 970 bytes --] Hi, I’m Priyansh, a student at IIITM Gwalior. I am applying for the erofs-rs GSoC project. I have been reading the code for a few weeks. I found a small issue in types.rs in the function xattr_size(). (count - 1) * size_of::<XattrEntry>() + size_of::<XattrHeader>() when count = 1, it returns only 12, which is just the header size but there is also one inline entry after the header, and it is not counted. In line 115 on filesystem.rs, this value is used to find where the inline file data starts. Because of this any FlatInline inode with exactly one xattr gives wrong file data. There is no error or panic, but the output is wrong. While working on this, I also built a working inline xattr parser on a real test image: https://github.com/priyansh-saxena1/erofs-rs/tree/xattr-poc I will send a patch soon to fix xattr_size(). I have also submitted my GSoC proposal. Thanks, Priyansh Saxena https://github.com/priyansh-saxena1 [-- Attachment #2: Type: text/html, Size: 2859 bytes --] ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH] erofs-rs: fix inline xattr size for tail offsets 2026-03-31 13:31 [GSoC 2026] erofs-rs xattr POC + xattr_size() fix Priyansh Saxena @ 2026-04-09 15:48 ` priyena.programming 2026-04-10 15:41 ` [PATCH v2] " priyena.programming 0 siblings, 1 reply; 4+ messages in thread From: priyena.programming @ 2026-04-09 15:48 UTC (permalink / raw) To: linux-erofs; +Cc: Transcendental-Programmer From: Transcendental-Programmer <priyena.programming@gmail.com> --- 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 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH v2] erofs-rs: fix inline xattr size for tail offsets 2026-04-09 15:48 ` [PATCH] erofs-rs: fix inline xattr size for tail offsets priyena.programming @ 2026-04-10 15:41 ` priyena.programming 2026-04-10 15:44 ` Gao Xiang 0 siblings, 1 reply; 4+ messages in thread From: priyena.programming @ 2026-04-10 15:41 UTC (permalink / raw) To: linux-erofs; +Cc: Transcendental-Programmer From: Transcendental-Programmer <priyena.programming@gmail.com> --- 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 ^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH v2] erofs-rs: fix inline xattr size for tail offsets 2026-04-10 15:41 ` [PATCH v2] " priyena.programming @ 2026-04-10 15:44 ` Gao Xiang 0 siblings, 0 replies; 4+ messages in thread From: Gao Xiang @ 2026-04-10 15:44 UTC (permalink / raw) To: priyena.programming, linux-erofs Please submit PR directly to Dreamacro/erofs-rs. ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-10 15:44 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-31 13:31 [GSoC 2026] erofs-rs xattr POC + xattr_size() fix Priyansh Saxena 2026-04-09 15:48 ` [PATCH] erofs-rs: fix inline xattr size for tail offsets priyena.programming 2026-04-10 15:41 ` [PATCH v2] " priyena.programming 2026-04-10 15:44 ` Gao Xiang
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox