From: Eric Biggers <ebiggers@xxxxxxxxxx> Both the kernel and e2fsck expect that if an inode has inline data, then it contains a "system.data" xattr -- even if i_size <= 60 so the data fits entirely in i_block. But if a symlink of exactly 60 bytes (not counting a NUL terminator) was created using ext2fs_symlink() and the inline data feature was enabled, then the symlink inode ended up with inline data but without a system.data xattr. This is possible because "fast" symlinks store a NUL terminator but inline data symlinks do not. So 60 bytes is too long for a real fast symlink, but still short enough to fit the entire target in i_block as a "slow" symlink using inline data. Some places use ext2fs_inline_data_init() to ensure the system.data xattr is created, but the symlink path does not. Fix this by making ext2fs_inline_data_set() set system.data to an empty string when i_size <= 60. Fixes: 54e880b870f7 ("libext2fs: handle inline data in read/write function") Signed-off-by: Eric Biggers <ebiggers@xxxxxxxxxx> --- lib/ext2fs/inline_data.c | 50 +++++++++++++++++----------------- tests/f_create_symlinks/expect | 20 +++++++++++--- tests/f_create_symlinks/script | 4 +-- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/lib/ext2fs/inline_data.c b/lib/ext2fs/inline_data.c index 7215c517..0cf80b9a 100644 --- a/lib/ext2fs/inline_data.c +++ b/lib/ext2fs/inline_data.c @@ -535,7 +535,10 @@ errcode_t ext2fs_inline_data_set(ext2_filsys fs, ext2_ino_t ino, void *buf, size_t size) { struct ext2_inode inode_buf; - struct ext2_inline_data data; + struct ext2_inline_data data = { + .fs = fs, + .ino = ino, + }; errcode_t retval; size_t free_ea_size, existing_size, free_inode_size; @@ -548,37 +551,34 @@ errcode_t ext2fs_inline_data_set(ext2_filsys fs, ext2_ino_t ino, if (size <= EXT4_MIN_INLINE_DATA_SIZE) { memcpy((void *)inode->i_block, buf, size); - return ext2fs_write_inode(fs, ino, inode); - } - - retval = ext2fs_xattr_inode_max_size(fs, ino, &free_ea_size); - if (retval) - return retval; + } else { + retval = ext2fs_xattr_inode_max_size(fs, ino, &free_ea_size); + if (retval) + return retval; - retval = ext2fs_inline_data_size(fs, ino, &existing_size); - if (retval) - return retval; + retval = ext2fs_inline_data_size(fs, ino, &existing_size); + if (retval) + return retval; - if (existing_size < EXT4_MIN_INLINE_DATA_SIZE) - free_inode_size = EXT4_MIN_INLINE_DATA_SIZE - existing_size; - else - free_inode_size = 0; + if (existing_size < EXT4_MIN_INLINE_DATA_SIZE) { + free_inode_size = EXT4_MIN_INLINE_DATA_SIZE - + existing_size; + } else { + free_inode_size = 0; + } - if (size != existing_size && - size > existing_size + free_ea_size + free_inode_size) - return EXT2_ET_INLINE_DATA_NO_SPACE; + if (size != existing_size && + size > existing_size + free_ea_size + free_inode_size) + return EXT2_ET_INLINE_DATA_NO_SPACE; - memcpy((void *)inode->i_block, buf, EXT4_MIN_INLINE_DATA_SIZE); + memcpy((void *)inode->i_block, buf, EXT4_MIN_INLINE_DATA_SIZE); + if (size > EXT4_MIN_INLINE_DATA_SIZE) + data.ea_size = size - EXT4_MIN_INLINE_DATA_SIZE; + data.ea_data = (char *) buf + EXT4_MIN_INLINE_DATA_SIZE; + } retval = ext2fs_write_inode(fs, ino, inode); if (retval) return retval; - data.fs = fs; - data.ino = ino; - if (size > EXT4_MIN_INLINE_DATA_SIZE) - data.ea_size = size - EXT4_MIN_INLINE_DATA_SIZE; - else - data.ea_size = 0; - data.ea_data = (char *) buf + EXT4_MIN_INLINE_DATA_SIZE; return ext2fs_inline_data_ea_set(&data); } diff --git a/tests/f_create_symlinks/expect b/tests/f_create_symlinks/expect index 1794e0a7..3033f8aa 100644 --- a/tests/f_create_symlinks/expect +++ b/tests/f_create_symlinks/expect @@ -7,6 +7,7 @@ Pass 5: Checking group summary information test_filesys: 11/128 files (0.0% non-contiguous), 441/1024 blocks Exit status is 0 debugfs -R "symlink /l_30 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" test.img +debugfs -R "symlink /l_60 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" test.img debugfs -R "symlink /l_70 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" test.img debugfs -R "symlink /l_500 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" test.img debugfs -R "symlink /l_1023 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" test.img @@ -25,9 +26,20 @@ Links: 1 Blockcount: 0 Fragment: Address: 0 Number: 0 Size: 0 Size of extra inode fields: 32 Fast link dest: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -debugfs -R "stat /l_70" test.img +debugfs -R "stat /l_60" test.img Inode: 13 Type: symlink Mode: 0777 Flags: 0x10000000 Generation: 0 Version: 0x00000000:00000000 +User: 0 Group: 0 Project: 0 Size: 60 +File ACL: 0 +Links: 1 Blockcount: 0 +Fragment: Address: 0 Number: 0 Size: 0 +Size of extra inode fields: 32 +Extended attributes: + system.data (0) +Fast link dest: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +debugfs -R "stat /l_70" test.img +Inode: 14 Type: symlink Mode: 0777 Flags: 0x10000000 +Generation: 0 Version: 0x00000000:00000000 User: 0 Group: 0 Project: 0 Size: 70 File ACL: 0 Links: 1 Blockcount: 0 @@ -37,7 +49,7 @@ Extended attributes: system.data (10) Fast link dest: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" debugfs -R "stat /l_500" test.img -Inode: 14 Type: symlink Mode: 0777 Flags: 0x80000 +Inode: 15 Type: symlink Mode: 0777 Flags: 0x80000 Generation: 0 Version: 0x00000000:00000000 User: 0 Group: 0 Project: 0 Size: 500 File ACL: 0 @@ -47,7 +59,7 @@ Size of extra inode fields: 32 EXTENTS: (0):153 debugfs -R "stat /l_1023" test.img -Inode: 15 Type: symlink Mode: 0777 Flags: 0x80000 +Inode: 16 Type: symlink Mode: 0777 Flags: 0x80000 Generation: 0 Version: 0x00000000:00000000 User: 0 Group: 0 Project: 0 Size: 1023 File ACL: 0 @@ -65,5 +77,5 @@ Pass 2: Checking directory structure Pass 3: Checking directory connectivity Pass 4: Checking reference counts Pass 5: Checking group summary information -test_filesys: 15/128 files (0.0% non-contiguous), 443/1024 blocks +test_filesys: 16/128 files (0.0% non-contiguous), 443/1024 blocks Exit status is 0 diff --git a/tests/f_create_symlinks/script b/tests/f_create_symlinks/script index 73f95a6e..1a97216a 100644 --- a/tests/f_create_symlinks/script +++ b/tests/f_create_symlinks/script @@ -23,13 +23,13 @@ echo Exit status is $status >> $OUT.new sed -f $cmd_dir/filter.sed -e "s;$TMPFILE;test.img;" $OUT.new >> $OUT rm -f $OUT.new -for i in 30 70 500 1023 1024 1500; do +for i in 30 60 70 500 1023 1024 1500; do echo "debugfs -R \"symlink /l_$i $(perl -e "print 'x' x $i;")\" test.img" >> $OUT $DEBUGFS -w -R "symlink /l_$i $(perl -e "print 'x' x $i;")" $TMPFILE \ 2>&1 | sed -f $cmd_dir/filter.sed >> $OUT done -for i in 30 70 500 1023 1024 1500; do +for i in 30 60 70 500 1023 1024 1500; do echo "debugfs -R \"stat /l_$i\" test.img" >> $OUT $DEBUGFS -R "stat /l_$i" $TMPFILE 2>&1 | \ sed -f $cmd_dir/filter.sed | grep -v "time: " >> $OUT -- 2.16.2.395.g2e18187dfd-goog