2008-01-09 20:04:10

by Jan Kara

[permalink] [raw]
Subject: [PATCH resend] e2fsprogs: Handle rec_len correctly for 64KB blocksize

Hi,

this is a resend of the new version of 64KB blocksize support for
e2fsprogs. The patch went through testing by a script which is also
attached to this email. Ted, would you be interested if I modify the test
script so that tests in it can be run when user does 'make test'?

Honza
--
Jan Kara <[email protected]>
SUSE Labs, CR
---

Subject: Support for 64KB blocksize in ext2-4 directories.

When block size is 64KB, we have to take care that rec_len does not overflow.
Kernel stores 0xffff in case 0x10000 should be stored - perform appropriate
conversion when processing directories.

Signed-off-by: Jan Kara <[email protected]>

diff --git a/debugfs/htree.c b/debugfs/htree.c
index d0e673e..a326241 100644
--- a/debugfs/htree.c
+++ b/debugfs/htree.c
@@ -40,6 +40,7 @@ static void htree_dump_leaf_node(ext2_fi
blk_t pblk;
ext2_dirhash_t hash;
int hash_alg;
+ int rec_len;

errcode = ext2fs_bmap(fs, ino, inode, buf, 0, blk, &pblk);
if (errcode) {
@@ -61,10 +62,8 @@ static void htree_dump_leaf_node(ext2_fi

while (offset < fs->blocksize) {
dirent = (struct ext2_dir_entry *) (buf + offset);
- if (((offset + dirent->rec_len) > fs->blocksize) ||
- (dirent->rec_len < 8) ||
- ((dirent->rec_len % 4) != 0) ||
- (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
+ if (ext2fs_validate_dirent(fs, offset, dirent) < 0) {
fprintf(pager, "Corrupted directory block (%u)!\n", blk);
break;
}
@@ -79,7 +78,7 @@ static void htree_dump_leaf_node(ext2_fi
com_err("htree_dump_leaf_node", errcode,
"while calculating hash");
sprintf(tmp, "%u 0x%08x (%d) %s ", dirent->inode,
- hash, dirent->rec_len, name);
+ hash, rec_len, name);
thislen = strlen(tmp);
if (col + thislen > 80) {
fprintf(pager, "\n");
@@ -87,7 +86,7 @@ static void htree_dump_leaf_node(ext2_fi
}
fprintf(pager, "%s", tmp);
col += thislen;
- offset += dirent->rec_len;
+ offset += rec_len;
}
fprintf(pager, "\n");
}
@@ -389,7 +388,7 @@ static int search_dir_block(ext2_filsys
printf("offset %u\n", offset);
return BLOCK_ABORT;
}
- offset += dirent->rec_len;
+ offset += ext2fs_rec_len_from_disk(dirent->rec_len);
}
return 0;
}
diff --git a/debugfs/ls.c b/debugfs/ls.c
index 52c7e34..1960c11 100644
--- a/debugfs/ls.c
+++ b/debugfs/ls.c
@@ -97,7 +97,7 @@ static int list_dir_proc(ext2_ino_t dir
fprintf (ls->f, " %s %s\n", datestr, name);
} else {
sprintf(tmp, "%c%u%c (%d) %s ", lbr, dirent->inode, rbr,
- dirent->rec_len, name);
+ ext2fs_rec_len_from_disk(dirent->rec_len), name);
thislen = strlen(tmp);

if (ls->col + thislen > 80) {
diff --git a/e2fsck/message.c b/e2fsck/message.c
index b2e3e0f..05b2e17 100644
--- a/e2fsck/message.c
+++ b/e2fsck/message.c
@@ -342,12 +342,13 @@ static _INLINE_ void expand_dirent_expre
struct problem_context *ctx)
{
struct ext2_dir_entry *dirent;
- int len;
+ int len, rec_len;

if (!ctx || !ctx->dirent)
goto no_dirent;

dirent = ctx->dirent;
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);

switch (ch) {
case 'i':
@@ -357,12 +358,12 @@ static _INLINE_ void expand_dirent_expre
len = dirent->name_len & 0xFF;
if (len > EXT2_NAME_LEN)
len = EXT2_NAME_LEN;
- if (len > dirent->rec_len)
- len = dirent->rec_len;
+ if (len > rec_len)
+ len = rec_len;
safe_print(dirent->name, len);
break;
case 'r':
- printf("%u", dirent->rec_len);
+ printf("%u", rec_len);
break;
case 'l':
printf("%u", dirent->name_len & 0xFF);
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index ceb9c7f..fd2c7d0 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -379,6 +379,7 @@ static void check_is_really_dir(e2fsck_t
errcode_t retval;
blk_t blk;
int i, not_device = 0;
+ int rec_len;

if (LINUX_S_ISDIR(inode->i_mode) || LINUX_S_ISREG(inode->i_mode) ||
LINUX_S_ISLNK(inode->i_mode) || inode->i_block[0] == 0)
@@ -408,20 +409,22 @@ static void check_is_really_dir(e2fsck_t
return;

dirent = (struct ext2_dir_entry *) buf;
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
if (((dirent->name_len & 0xFF) != 1) ||
(dirent->name[0] != '.') ||
(dirent->inode != pctx->ino) ||
- (dirent->rec_len < 12) ||
- (dirent->rec_len % 4) ||
- (dirent->rec_len >= ctx->fs->blocksize - 12))
+ (rec_len < 12) ||
+ (rec_len % 4) ||
+ (rec_len >= ctx->fs->blocksize - 12))
return;

dirent = (struct ext2_dir_entry *) (buf + dirent->rec_len);
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
if (((dirent->name_len & 0xFF) != 2) ||
(dirent->name[0] != '.') ||
(dirent->name[1] != '.') ||
- (dirent->rec_len < 12) ||
- (dirent->rec_len % 4))
+ (rec_len < 12) ||
+ (rec_len % 4))
return;

if (fix_problem(ctx, PR_1_TREAT_AS_DIRECTORY, pctx)) {
diff --git a/e2fsck/pass2.c b/e2fsck/pass2.c
index 27f7136..3080326 100644
--- a/e2fsck/pass2.c
+++ b/e2fsck/pass2.c
@@ -365,6 +365,7 @@ static int check_dot(e2fsck_t ctx,
int created = 0;
int new_len;
int problem = 0;
+ int rec_len;

if (!dirent->inode)
problem = PR_2_MISSING_DOT;
@@ -374,10 +375,11 @@ static int check_dot(e2fsck_t ctx,
else if (dirent->name[1] != '\0')
problem = PR_2_DOT_NULL_TERM;

+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
if (problem) {
if (fix_problem(ctx, problem, pctx)) {
- if (dirent->rec_len < 12)
- dirent->rec_len = 12;
+ if (rec_len < 12)
+ dirent->rec_len = ext2fs_rec_len_to_disk(12);
dirent->inode = ino;
dirent->name_len = 1;
dirent->name[0] = '.';
@@ -392,15 +394,15 @@ static int check_dot(e2fsck_t ctx,
status = 1;
}
}
- if (dirent->rec_len > 12) {
- new_len = dirent->rec_len - 12;
+ if (rec_len > 12) {
+ new_len = rec_len - 12;
if (new_len > 12) {
if (created ||
fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) {
nextdir = (struct ext2_dir_entry *)
((char *) dirent + 12);
- dirent->rec_len = 12;
- nextdir->rec_len = new_len;
+ dirent->rec_len = ext2fs_rec_len_to_disk(12);
+ nextdir->rec_len = ext2fs_rec_len_to_disk(new_len);
nextdir->inode = 0;
nextdir->name_len = 0;
status = 1;
@@ -432,8 +434,8 @@ static int check_dotdot(e2fsck_t ctx,

if (problem) {
if (fix_problem(ctx, problem, pctx)) {
- if (dirent->rec_len < 12)
- dirent->rec_len = 12;
+ if (ext2fs_rec_len_from_disk(dirent->rec_len) < 12)
+ dirent->rec_len = ext2fs_rec_len_to_disk(12);
/*
* Note: we don't have the parent inode just
* yet, so we will fill it in with the root
@@ -652,14 +654,15 @@ static void salvage_directory(ext2_filsy
unsigned int *offset)
{
char *cp = (char *) dirent;
- int left = fs->blocksize - *offset - dirent->rec_len;
+ int rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
+ int left = fs->blocksize - *offset - rec_len;
unsigned int name_len = dirent->name_len & 0xFF;

/*
* Special case of directory entry of size 8: copy what's left
* of the directory block up to cover up the invalid hole.
*/
- if ((left >= 12) && (dirent->rec_len == 8)) {
+ if ((left >= 12) && (rec_len == 8)) {
memmove(cp, cp+8, left);
memset(cp + left, 0, 8);
return;
@@ -670,10 +673,10 @@ static void salvage_directory(ext2_filsy
* record length.
*/
if ((left < 0) &&
- (name_len + 8 <= dirent->rec_len + (unsigned) left) &&
+ (name_len + 8 <= rec_len + (unsigned) left) &&
dirent->inode <= fs->super->s_inodes_count &&
strnlen(dirent->name, name_len) == name_len) {
- dirent->rec_len += left;
+ dirent->rec_len = ext2fs_rec_len_to_disk(rec_len + left);
return;
}
/*
@@ -681,10 +684,11 @@ static void salvage_directory(ext2_filsy
* of four, and not too big, such that it is valid, let the
* previous directory entry absorb the invalid one.
*/
- if (prev && dirent->rec_len && (dirent->rec_len % 4) == 0 &&
- (*offset + dirent->rec_len <= fs->blocksize)) {
- prev->rec_len += dirent->rec_len;
- *offset += dirent->rec_len;
+ if (prev && rec_len && (rec_len % 4) == 0 &&
+ (*offset + rec_len <= fs->blocksize)) {
+ prev->rec_len = ext2fs_rec_len_to_disk(
+ ext2fs_rec_len_from_disk(prev->rec_len) + rec_len);
+ *offset += rec_len;
return;
}
/*
@@ -694,10 +698,13 @@ static void salvage_directory(ext2_filsy
* new empty directory entry the rest of the directory block.
*/
if (prev) {
- prev->rec_len += fs->blocksize - *offset;
+ prev->rec_len = ext2fs_rec_len_to_disk(
+ ext2fs_rec_len_from_disk(prev->rec_len) +
+ fs->blocksize - *offset);
*offset = fs->blocksize;
} else {
- dirent->rec_len = fs->blocksize - *offset;
+ dirent->rec_len = ext2fs_rec_len_to_disk(
+ fs->blocksize - *offset);
dirent->name_len = 0;
dirent->inode = 0;
}
@@ -731,6 +738,7 @@ static int check_dir_block(ext2_filsys f
struct problem_context pctx;
int dups_found = 0;
int ret;
+ int rec_len;

cd = (struct check_dir_struct *) priv_data;
buf = cd->buf;
@@ -802,6 +810,7 @@ static int check_dir_block(ext2_filsys f
dx_db->max_hash = 0;

dirent = (struct ext2_dir_entry *) buf;
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
limit = (struct ext2_dx_countlimit *) (buf+8);
if (db->blockcnt == 0) {
root = (struct ext2_dx_root_info *) (buf + 24);
@@ -821,7 +830,7 @@ static int check_dir_block(ext2_filsys f
dx_dir->hashversion += 3;
dx_dir->depth = root->indirect_levels + 1;
} else if ((dirent->inode == 0) &&
- (dirent->rec_len == fs->blocksize) &&
+ (rec_len == fs->blocksize) &&
(dirent->name_len == 0) &&
(ext2fs_le16_to_cpu(limit->limit) ==
((fs->blocksize-8) /
@@ -835,12 +844,13 @@ static int check_dir_block(ext2_filsys f
do {
problem = 0;
dirent = (struct ext2_dir_entry *) (buf + offset);
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
cd->pctx.dirent = dirent;
cd->pctx.num = offset;
- if (((offset + dirent->rec_len) > fs->blocksize) ||
- (dirent->rec_len < 12) ||
- ((dirent->rec_len % 4) != 0) ||
- (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+ if (((offset + rec_len) > fs->blocksize) ||
+ (rec_len < 12) ||
+ ((rec_len % 4) != 0) ||
+ (((dirent->name_len & 0xFF)+8) > rec_len)) {
if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) {
salvage_directory(fs, dirent, prev, &offset);
dir_modified++;
@@ -1035,7 +1045,7 @@ static int check_dir_block(ext2_filsys f
ctx->fs_total_count++;
next:
prev = dirent;
- offset += dirent->rec_len;
+ offset += ext2fs_rec_len_from_disk(dirent->rec_len);
dot_state++;
} while (offset < fs->blocksize);
#if 0
@@ -1055,9 +1065,10 @@ static int check_dir_block(ext2_filsys f
}
#endif /* ENABLE_HTREE */
if (offset != fs->blocksize) {
- cd->pctx.num = dirent->rec_len - fs->blocksize + offset;
+ cd->pctx.num = ext2fs_rec_len_from_disk(dirent->rec_len)
+ - fs->blocksize + offset;
if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) {
- dirent->rec_len = cd->pctx.num;
+ dirent->rec_len = ext2fs_rec_len_to_disk(cd->pctx.num);
dir_modified++;
}
}
diff --git a/e2fsck/rehash.c b/e2fsck/rehash.c
index 8c1459c..2b70f98 100644
--- a/e2fsck/rehash.c
+++ b/e2fsck/rehash.c
@@ -102,7 +102,7 @@ static int fill_dir_block(ext2_filsys fs
if (HOLE_BLKADDR(*block_nr)) {
memset(dir, 0, fs->blocksize);
dirent = (struct ext2_dir_entry *) dir;
- dirent->rec_len = fs->blocksize;
+ dirent->rec_len = ext2fs_rec_len_to_disk(fs->blocksize);
} else {
fd->err = ext2fs_read_dir_block(fs, *block_nr, dir);
if (fd->err)
@@ -116,14 +116,11 @@ static int fill_dir_block(ext2_filsys fs
dir_offset = 0;
while (dir_offset < fs->blocksize) {
dirent = (struct ext2_dir_entry *) (dir + dir_offset);
- if (((dir_offset + dirent->rec_len) > fs->blocksize) ||
- (dirent->rec_len < 8) ||
- ((dirent->rec_len % 4) != 0) ||
- (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+ if (ext2fs_validate_dirent(fs, dir_offset, dirent) < 0) {
fd->err = EXT2_ET_DIR_CORRUPTED;
return BLOCK_ABORT;
}
- dir_offset += dirent->rec_len;
+ dir_offset += ext2fs_rec_len_from_disk(dirent->rec_len);
if (dirent->inode == 0)
continue;
if (!fd->compress && ((dirent->name_len&0xFF) == 1) &&
@@ -416,7 +413,9 @@ static errcode_t copy_dir_entries(ext2_f
rec_len = EXT2_DIR_REC_LEN(ent->dir->name_len & 0xFF);
if (rec_len > left) {
if (left)
- dirent->rec_len += left;
+ dirent->rec_len = ext2fs_rec_len_to_disk(
+ ext2fs_rec_len_from_disk(dirent->rec_len) +
+ left);
if ((retval = get_next_block(fs, outdir,
&block_start)))
return retval;
@@ -432,19 +431,20 @@ static errcode_t copy_dir_entries(ext2_f
}
dirent->inode = ent->dir->inode;
dirent->name_len = ent->dir->name_len;
- dirent->rec_len = rec_len;
+ dirent->rec_len = ext2fs_rec_len_to_disk(rec_len);
memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF);
offset += rec_len;
left -= rec_len;
if (left < 12) {
- dirent->rec_len += left;
+ dirent->rec_len = ext2fs_rec_len_to_disk(rec_len + left);
offset += left;
left = 0;
}
prev_hash = ent->hash;
}
if (left)
- dirent->rec_len += left;
+ dirent->rec_len = ext2fs_rec_len_to_disk(
+ ext2fs_rec_len_from_disk(dirent->rec_len) + left);

return 0;
}
@@ -466,13 +466,13 @@ static struct ext2_dx_root_info *set_roo
dir->inode = ino;
dir->name[0] = '.';
dir->name_len = 1 | filetype;
- dir->rec_len = 12;
+ dir->rec_len = ext2fs_rec_len_to_disk(12);
dir = (struct ext2_dir_entry *) (buf + 12);
dir->inode = parent;
dir->name[0] = '.';
dir->name[1] = '.';
dir->name_len = 2 | filetype;
- dir->rec_len = fs->blocksize - 12;
+ dir->rec_len = ext2fs_rec_len_to_disk(fs->blocksize - 12);

root = (struct ext2_dx_root_info *) (buf+24);
root->reserved_zero = 0;
@@ -497,7 +497,7 @@ static struct ext2_dx_entry *set_int_nod
memset(buf, 0, fs->blocksize);
dir = (struct ext2_dir_entry *) buf;
dir->inode = 0;
- dir->rec_len = fs->blocksize;
+ dir->rec_len = ext2fs_rec_len_to_disk(fs->blocksize);

limits = (struct ext2_dx_countlimit *) (buf+8);
limits->limit = (fs->blocksize - 8) / sizeof(struct ext2_dx_entry);
diff --git a/ext2ed/dir_com.c b/ext2ed/dir_com.c
index c6b194e..d36d4b9 100644
--- a/ext2ed/dir_com.c
+++ b/ext2ed/dir_com.c
@@ -117,7 +117,7 @@ struct struct_file_info search_dir_entri
dir_entry_ptr=(struct ext2_dir_entry_2 *) (info.buffer+info.dir_entry_offset);

info.dir_entry_num++;
- next = dir_entry_ptr->rec_len;
+ next = ext2_rec_len_from_disk(dir_entry_ptr->rec_len);
if (!next)
next = file_system_info.block_size - info.dir_entry_offset;
info.dir_entry_offset += next;
@@ -463,7 +463,7 @@ Show the current search entry (info) in
if (dir_entry_ptr->name_len > (COLS - 55) && COLS > 55)
temp [COLS-55]=0;
wprintw (show_pad,"inode = %-8lu rec_len = %-4lu name_len = %-3lu name = %s\n", /* Display the various fields */
- dir_entry_ptr->inode,dir_entry_ptr->rec_len,dir_entry_ptr->name_len,temp);
+ dir_entry_ptr->inode,ext2fs_rec_len_from_disk(dir_entry_ptr->rec_len),dir_entry_ptr->name_len,temp);

show_pad_info.max_line++;

@@ -619,8 +619,8 @@ because it is of variable length.

if (strcasecmp ("rec_len",variable)==0) {
found=1;
- dir_entry_ptr->rec_len=(unsigned int) atol (value);
- wprintw (command_win,"Variable %s set to %lu\n",variable,dir_entry_ptr->rec_len);refresh_command_win ();
+ dir_entry_ptr->rec_len=ext2fs_rec_len_to_disk((unsigned int) atol (value));
+ wprintw (command_win,"Variable %s set to %lu\n",variable,ext2fs_rec_len_from_disk(dir_entry_ptr->rec_len));refresh_command_win ();

}

@@ -648,7 +648,7 @@ because it is of variable length.
temp [dir_entry_ptr->name_len]=0;
wmove (show_pad,file_info.dir_entry_num,0);
wprintw (show_pad,"inode = %-8lu rec_len = %-4lu name_len = %-3lu name = %s\n",
- dir_entry_ptr->inode,dir_entry_ptr->rec_len,dir_entry_ptr->name_len,temp);
+ dir_entry_ptr->inode,ext2fs_rec_len_from_disk(dir_entry_ptr->rec_len),dir_entry_ptr->name_len,temp);
wattrset (show_pad,A_NORMAL);
show_pad_info.line=file_info.dir_entry_num-show_pad_info.display_lines/2;
refresh_show_pad ();
diff --git a/ext2ed/disk.c b/ext2ed/disk.c
index d29c719..b602724 100644
--- a/ext2ed/disk.c
+++ b/ext2ed/disk.c
@@ -210,7 +210,7 @@ Just read from the current position into

if (current_type!=NULL)
if (strcmp (current_type->name,"ext2_dir_entry")==0)
- current_type->length=type_data.u.t_ext2_dir_entry.rec_len;
+ current_type->length=ext2_rec_len_from_disk(type_data.u.t_ext2_dir_entry.rec_len);

return (1);
}
diff --git a/ext2ed/ext2ed.h b/ext2ed/ext2ed.h
index deae516..7eb5b29 100644
--- a/ext2ed/ext2ed.h
+++ b/ext2ed/ext2ed.h
@@ -35,6 +35,7 @@ Copyright (C) 1995 Gadi Oxman
#define DEBUG /* Activate self-sanity checks */

#include <ext2fs/ext2_fs.h> /* Main kernel ext2 include file */
+#include <ext2fs/ext2fs.h>
#include <sys/stat.h>

#include <ncurses.h>
diff --git a/lib/ext2fs/dir_iterate.c b/lib/ext2fs/dir_iterate.c
index 003c0a3..63ea974 100644
--- a/lib/ext2fs/dir_iterate.c
+++ b/lib/ext2fs/dir_iterate.c
@@ -35,10 +35,8 @@ static int ext2fs_validate_entry(char *b

while (offset < final_offset) {
dirent = (struct ext2_dir_entry *)(buf + offset);
- offset += dirent->rec_len;
- if ((dirent->rec_len < 8) ||
- ((dirent->rec_len % 4) != 0) ||
- (((dirent->name_len & 0xFF)+8) > dirent->rec_len))
+ offset += ext2fs_rec_len_from_disk(dirent->rec_len);
+ if (ext2fs_validate_dirent(NULL, 0, dirent) < 0)
return 0;
}
return (offset == final_offset);
@@ -145,6 +143,7 @@ int ext2fs_process_dir_block(ext2_filsys
int changed = 0;
int do_abort = 0;
int entry, size;
+ int rec_len;
struct ext2_dir_entry *dirent;

if (blockcnt < 0)
@@ -158,10 +157,7 @@ int ext2fs_process_dir_block(ext2_filsys

while (offset < fs->blocksize) {
dirent = (struct ext2_dir_entry *) (ctx->buf + offset);
- if (((offset + dirent->rec_len) > fs->blocksize) ||
- (dirent->rec_len < 8) ||
- ((dirent->rec_len % 4) != 0) ||
- (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+ if (ext2fs_validate_dirent(fs, offset, dirent) < 0) {
ctx->errcode = EXT2_ET_DIR_CORRUPTED;
return BLOCK_ABORT;
}
@@ -185,16 +181,17 @@ int ext2fs_process_dir_block(ext2_filsys
break;
}
next:
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
if (next_real_entry == offset)
- next_real_entry += dirent->rec_len;
+ next_real_entry += rec_len;

if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) {
size = ((dirent->name_len & 0xFF) + 11) & ~3;

- if (dirent->rec_len != size) {
+ if (rec_len != size) {
unsigned int final_offset;

- final_offset = offset + dirent->rec_len;
+ final_offset = offset + rec_len;
offset += size;
while (offset < final_offset &&
!ext2fs_validate_entry(ctx->buf,
@@ -204,7 +201,7 @@ next:
continue;
}
}
- offset += dirent->rec_len;
+ offset += rec_len;
}

if (changed) {
diff --git a/lib/ext2fs/dirblock.c b/lib/ext2fs/dirblock.c
index fb20fa0..545a22b 100644
--- a/lib/ext2fs/dirblock.c
+++ b/lib/ext2fs/dirblock.c
@@ -25,7 +25,7 @@ errcode_t ext2fs_read_dir_block2(ext2_fi
errcode_t retval;
char *p, *end;
struct ext2_dir_entry *dirent;
- unsigned int name_len, rec_len;
+ unsigned int rec_len;


retval = io_channel_read_blk(fs->io, block, 1, buf);
@@ -39,20 +39,23 @@ errcode_t ext2fs_read_dir_block2(ext2_fi
#ifdef WORDS_BIGENDIAN
dirent->inode = ext2fs_swab32(dirent->inode);
dirent->rec_len = ext2fs_swab16(dirent->rec_len);
- dirent->name_len = ext2fs_swab16(dirent->name_len);
-#endif
- name_len = dirent->name_len;
-#ifdef WORDS_BIGENDIAN
- if (flags & EXT2_DIRBLOCK_V2_STRUCT)
+ if (!(flags & EXT2_DIRBLOCK_V2_STRUCT))
dirent->name_len = ext2fs_swab16(dirent->name_len);
#endif
- rec_len = dirent->rec_len;
- if ((rec_len < 8) || (rec_len % 4)) {
- rec_len = 8;
- retval = EXT2_ET_DIR_CORRUPTED;
+ rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
+ switch (ext2fs_validate_dirent(fs, p - (char *)buf, dirent)) {
+ case -1:
+ rec_len = end - p;
+ retval = EXT2_ET_DIR_CORRUPTED;
+ break;
+ case -2:
+ rec_len = 8;
+ retval = EXT2_ET_DIR_CORRUPTED;
+ break;
+ case -3:
+ retval = EXT2_ET_DIR_CORRUPTED;
+ break;
}
- if (((name_len & 0xFF) + 8) > dirent->rec_len)
- retval = EXT2_ET_DIR_CORRUPTED;
p += rec_len;
}
return retval;
@@ -73,6 +76,7 @@ errcode_t ext2fs_write_dir_block2(ext2_f
char *p, *end;
char *buf = 0;
struct ext2_dir_entry *dirent;
+ int rec_len;

retval = ext2fs_get_mem(fs->blocksize, &buf);
if (retval)
@@ -82,17 +86,14 @@ errcode_t ext2fs_write_dir_block2(ext2_f
end = buf + fs->blocksize;
while (p < end) {
dirent = (struct ext2_dir_entry *) p;
- if ((dirent->rec_len < 8) ||
- (dirent->rec_len % 4)) {
+ if (ext2fs_validate_dirent(fs, p-buf, dirent) < 0) {
ext2fs_free_mem(&buf);
return (EXT2_ET_DIR_CORRUPTED);
}
- p += dirent->rec_len;
+ p += ext2fs_rec_len_from_disk(dirent->rec_len);
dirent->inode = ext2fs_swab32(dirent->inode);
dirent->rec_len = ext2fs_swab16(dirent->rec_len);
- dirent->name_len = ext2fs_swab16(dirent->name_len);
-
- if (flags & EXT2_DIRBLOCK_V2_STRUCT)
+ if (!(flags & EXT2_DIRBLOCK_V2_STRUCT))
dirent->name_len = ext2fs_swab16(dirent->name_len);
}
retval = io_channel_write_blk(fs->io, block, 1, buf);
diff --git a/lib/ext2fs/ext2_fs.h b/lib/ext2fs/ext2_fs.h
index 36e7c8c..7b31c8b 100644
--- a/lib/ext2fs/ext2_fs.h
+++ b/lib/ext2fs/ext2_fs.h
@@ -724,6 +724,21 @@ struct ext2_dir_entry_2 {
#define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1)
#define EXT2_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT2_DIR_ROUND) & \
~EXT2_DIR_ROUND)
+#define EXT2_MAX_REC_LEN ((1<<16)-1)
+
+static inline unsigned ext2fs_rec_len_from_disk(unsigned len)
+{
+ if (len == EXT2_MAX_REC_LEN)
+ return 1 << 16;
+ return len;
+}
+
+static inline unsigned ext2fs_rec_len_to_disk(unsigned len)
+{
+ if (len == (1 << 16))
+ return EXT2_MAX_REC_LEN;
+ return len;
+}

/*
* This structure will be used for multiple mount protection. It will be
diff --git a/lib/ext2fs/ext2fs.h b/lib/ext2fs/ext2fs.h
index d691c1b..8b08c07 100644
--- a/lib/ext2fs/ext2fs.h
+++ b/lib/ext2fs/ext2fs.h
@@ -1003,6 +1003,8 @@ extern blk_t ext2fs_group_last_block(ext
extern blk_t ext2fs_inode_data_blocks(ext2_filsys fs,
struct ext2_inode *inode);
extern unsigned int ext2fs_div_ceil(unsigned int a, unsigned int b);
+extern int ext2fs_validate_dirent(ext2_filsys fs, unsigned int offset,
+ struct ext2_dir_entry *dirent);

/*
* The actual inlined functions definitions themselves...
@@ -1203,6 +1205,24 @@ _INLINE_ unsigned int ext2fs_div_ceil(un
return 0;
return ((a - 1) / b) + 1;
}
+
+/*
+ * Check whether directory entry is valid
+ */
+_INLINE_ int ext2fs_validate_dirent(ext2_filsys fs, unsigned int offset,
+ struct ext2_dir_entry *dirent)
+{
+ int rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);
+
+ if (fs && offset + rec_len > fs->blocksize)
+ return -1;
+ if (rec_len < 8 || rec_len % 4 != 0)
+ return -2;
+ if ((dirent->name_len & 0xFF)+8 > rec_len)
+ return -3;
+ return 0;
+}
+
#undef _INLINE_
#endif

diff --git a/lib/ext2fs/link.c b/lib/ext2fs/link.c
index 5e0f4f3..0838599 100644
--- a/lib/ext2fs/link.c
+++ b/lib/ext2fs/link.c
@@ -35,21 +35,26 @@ static int link_proc(struct ext2_dir_ent
{
struct link_struct *ls = (struct link_struct *) priv_data;
struct ext2_dir_entry *next;
- int rec_len, min_rec_len;
+ int rec_len, min_rec_len, n_rec_len, c_rec_len;
int ret = 0;

rec_len = EXT2_DIR_REC_LEN(ls->namelen);
+ c_rec_len = ext2fs_rec_len_from_disk(dirent->rec_len);

/*
* See if the following directory entry (if any) is unused;
* if so, absorb it into this one.
*/
- next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len);
- if ((offset + dirent->rec_len < blocksize - 8) &&
- (next->inode == 0) &&
- (offset + dirent->rec_len + next->rec_len <= blocksize)) {
- dirent->rec_len += next->rec_len;
- ret = DIRENT_CHANGED;
+ next = (struct ext2_dir_entry *) (buf + offset + c_rec_len);
+ if ((offset + c_rec_len < blocksize - 8) &&
+ (next->inode == 0)) {
+ n_rec_len = ext2fs_rec_len_from_disk(next->rec_len);
+ if (offset + c_rec_len + n_rec_len <= blocksize) {
+ dirent->rec_len =
+ ext2fs_rec_len_to_disk(c_rec_len + n_rec_len);
+ c_rec_len += n_rec_len;
+ ret = DIRENT_CHANGED;
+ }
}

/*
@@ -59,15 +64,15 @@ static int link_proc(struct ext2_dir_ent
*/
if (dirent->inode) {
min_rec_len = EXT2_DIR_REC_LEN(dirent->name_len & 0xFF);
- if (dirent->rec_len < (min_rec_len + rec_len))
+ if (c_rec_len < (min_rec_len + rec_len))
return ret;
- rec_len = dirent->rec_len - min_rec_len;
- dirent->rec_len = min_rec_len;
+ rec_len = c_rec_len - min_rec_len;
+ dirent->rec_len = ext2fs_rec_len_to_disk(min_rec_len);
next = (struct ext2_dir_entry *) (buf + offset +
- dirent->rec_len);
+ min_rec_len);
next->inode = 0;
next->name_len = 0;
- next->rec_len = rec_len;
+ next->rec_len = ext2fs_rec_len_to_disk(rec_len);
return DIRENT_CHANGED;
}

@@ -75,7 +80,7 @@ static int link_proc(struct ext2_dir_ent
* If we get this far, then the directory entry is not used.
* See if we can fit the request entry in. If so, do it.
*/
- if (dirent->rec_len < rec_len)
+ if (c_rec_len < rec_len)
return ret;
dirent->inode = ls->inode;
dirent->name_len = ls->namelen;
diff --git a/lib/ext2fs/newdir.c b/lib/ext2fs/newdir.c
index 3904d91..6a4b478 100644
--- a/lib/ext2fs/newdir.c
+++ b/lib/ext2fs/newdir.c
@@ -41,7 +41,7 @@ errcode_t ext2fs_new_dir_block(ext2_fils
return retval;
memset(buf, 0, fs->blocksize);
dir = (struct ext2_dir_entry *) buf;
- dir->rec_len = fs->blocksize;
+ dir->rec_len = ext2fs_rec_len_to_disk(fs->blocksize);

if (dir_ino) {
if (fs->super->s_feature_incompat &
@@ -53,14 +53,16 @@ errcode_t ext2fs_new_dir_block(ext2_fils
dir->inode = dir_ino;
dir->name_len = 1 | filetype;
dir->name[0] = '.';
- rec_len = dir->rec_len - EXT2_DIR_REC_LEN(1);
- dir->rec_len = EXT2_DIR_REC_LEN(1);
+ rec_len = ext2fs_rec_len_from_disk(dir->rec_len)
+ - EXT2_DIR_REC_LEN(1);
+ dir->rec_len = ext2fs_rec_len_to_disk(EXT2_DIR_REC_LEN(1));

/*
* Set up entry for '..'
*/
- dir = (struct ext2_dir_entry *) (buf + dir->rec_len);
- dir->rec_len = rec_len;
+ dir = (struct ext2_dir_entry *) (buf +
+ ext2fs_rec_len_from_disk(dir->rec_len));
+ dir->rec_len = ext2fs_rec_len_to_disk(rec_len);
dir->inode = parent_ino;
dir->name_len = 2 | filetype;
dir->name[0] = '.';
diff --git a/lib/ext2fs/unlink.c b/lib/ext2fs/unlink.c
index 31dd8ec..f5f34dc 100644
--- a/lib/ext2fs/unlink.c
+++ b/lib/ext2fs/unlink.c
@@ -57,7 +57,12 @@ static int unlink_proc(struct ext2_dir_e
}

if (offset)
- prev->rec_len += dirent->rec_len;
+ /* We actually would not need to convert initial values as
+ * they are certainly less than 64K but let's not try to be
+ * too clever */
+ prev->rec_len = ext2fs_rec_len_to_disk(
+ ext2fs_rec_len_from_disk(prev->rec_len) +
+ ext2fs_rec_len_from_disk(dirent->rec_len));
else
dirent->inode = 0;
ls->done++;
diff --git a/misc/e2image.c b/misc/e2image.c
index 1fbb267..3006af2 100644
--- a/misc/e2image.c
+++ b/misc/e2image.c
@@ -345,25 +345,29 @@ static void scramble_dir_block(ext2_fils
end = buf + fs->blocksize;
for (p = buf; p < end-8; p += rec_len) {
dirent = (struct ext2_dir_entry_2 *) p;
- rec_len = dirent->rec_len;
#ifdef WORDS_BIGENDIAN
- rec_len = ext2fs_swab16(rec_len);
+ rec_len = ext2fs_swab16(dirent->rec_len);
+#else
+ rec_len = dirent->rec_len;
#endif
+ rec_len = ext2fs_rec_len_from_disk(rec_len);
#if 0
printf("rec_len = %d, name_len = %d\n", rec_len, dirent->name_len);
#endif
- if (rec_len < 8 || (rec_len % 4) ||
- (p+rec_len > end)) {
+ switch (ext2fs_validate_dirent(fs, p - buf, (struct ext2_dir_entry *)dirent)) {
+ case -1:
+ case -2:
printf("Corrupt directory block %lu: "
"bad rec_len (%d)\n", (unsigned long) blk,
rec_len);
- rec_len = end - p;
+ rec_len = ext2fs_rec_len_to_disk(end - p);
#ifdef WORDS_BIGENDIAN
- dirent->rec_len = ext2fs_swab16(rec_len);
+ dirent->rec_len = ext2fs_swab16(rec_len);
+#else
+ dirent->rec_len = rec_len;
#endif
continue;
- }
- if (dirent->name_len + 8 > rec_len) {
+ case -3:
printf("Corrupt directory block %lu: "
"bad name_len (%d)\n", (unsigned long) blk,
dirent->name_len);


Attachments:
(No filename) (28.07 kB)
64KB_test.tar.gz (40.26 kB)
Download all attachments