This looks similar to one of a few crashes that I saw with
upstream dosfstools-2.11 due to zero slot numbers causing a
negative offset in the call to copy_lfn_part in lfn_add_slot. On
amd64 this results in a SIGSEGV in copy_lfn_part. On x86 the
result is heap corruption and thus sometimes a SIGSEGV or double
free abort later.
This patch resolves the issues for me.
--- orig/dosfsck/lfn.c 2005-03-13 04:05:44.000000000 +1300
+++ dosfsck/lfn.c 2008-06-26 22:49:36.000000000 +1200
@@ -143,34 +143,37 @@ void lfn_reset( void )
}
/* This function is only called with de->attr == VFAT_LN_ATTR. It stores part
* of the long name. */
void lfn_add_slot( DIR_ENT *de, loff_t dir_offset )
{
LFN_ENT *lfn = (LFN_ENT *)de;
+ int slot = lfn->id & LFN_ID_SLOTMASK;
unsigned offset;
+ if (lfn_slot == 0) lfn_check_orphaned();
+
if (de->attr != VFAT_LN_ATTR)
die("lfn_add_slot called with non-LFN directory entry");
- if (lfn->id & LFN_ID_START) {
+ if (lfn->id & LFN_ID_START && slot != 0) {
if (lfn_slot != -1) {
int can_clear = 0;
/* There is already a LFN "in progess", so it is an error that a
* new start entry is here. */
/* Causes: 1) if slot# == expected: start bit set mysteriously, 2)
* old LFN overwritten by new one */
/* Fixes: 1) delete previous LFN 2) if slot# == expected and
* checksum ok: clear start bit */
/* XXX: Should delay that until next LFN known (then can better
* display the name) */
printf( "A new long file name starts within an old one.\n" );
- if ((lfn->id & LFN_ID_SLOTMASK) == lfn_slot &&
+ if (slot == lfn_slot &&
lfn->alias_checksum == lfn_checksum) {
char *part1 = CNV_THIS_PART(lfn);
char *part2 = CNV_PARTS_SO_FAR();
printf( " It could be that the LFN start bit is wrong here\n"
" if \"%s\" seems to match \"%s\".\n", part1, part2 );
free( part1 );
free( part2 );
can_clear = 1;
@@ -192,102 +195,102 @@ void lfn_add_slot( DIR_ENT *de, loff_t d
case '3':
lfn->id &= ~LFN_ID_START;
fs_write( dir_offset+offsetof(LFN_ENT,id),
sizeof(lfn->id), &lfn->id );
break;
}
}
}
- lfn_slot = lfn->id & LFN_ID_SLOTMASK;
+ lfn_slot = slot;
lfn_checksum = lfn->alias_checksum;
lfn_unicode = alloc( (lfn_slot*CHARS_PER_LFN+1)*2 );
lfn_offsets = alloc( lfn_slot*sizeof(loff_t) );
lfn_parts = 0;
}
- else if (lfn_slot == -1) {
+ else if (lfn_slot == -1 && slot != 0) {
/* No LFN in progress, but slot found; start bit missing */
/* Causes: 1) start bit got lost, 2) Previous slot with start bit got
* lost */
/* Fixes: 1) delete LFN, 2) set start bit */
char *part = CNV_THIS_PART(lfn);
printf( "Long filename fragment \"%s\" found outside a LFN "
"sequence.\n (Maybe the start bit is missing on the "
"last fragment)\n", part );
if (interactive) {
printf( "1: Delete fragment\n2: Leave it as it is.\n"
"3: Set start bit\n" );
}
else printf( " Not auto-correcting this.\n" );
- if (interactive) {
- switch( get_key( "123", "?" )) {
- case '1':
- if (!lfn_offsets)
- lfn_offsets = alloc( sizeof(loff_t) );
- lfn_offsets[0] = dir_offset;
- clear_lfn_slots( 0, 0 );
- lfn_reset();
- return;
- case '2':
- lfn_reset();
- return;
- case '3':
- lfn->id |= LFN_ID_START;
- fs_write( dir_offset+offsetof(LFN_ENT,id),
- sizeof(lfn->id), &lfn->id );
- lfn_slot = lfn->id & LFN_ID_SLOTMASK;
- lfn_checksum = lfn->alias_checksum;
- lfn_unicode = alloc( (lfn_slot*CHARS_PER_LFN+1)*2 );
- lfn_offsets = alloc( lfn_slot*sizeof(loff_t) );
- lfn_parts = 0;
- break;
- }
+ switch( interactive ? get_key( "123", "?" ) : '2') {
+ case '1':
+ if (!lfn_offsets)
+ lfn_offsets = alloc( sizeof(loff_t) );
+ lfn_offsets[0] = dir_offset;
+ clear_lfn_slots( 0, 0 );
+ lfn_reset();
+ return;
+ case '2':
+ lfn_reset();
+ return;
+ case '3':
+ lfn->id |= LFN_ID_START;
+ fs_write( dir_offset+offsetof(LFN_ENT,id),
+ sizeof(lfn->id), &lfn->id );
+ lfn_slot = slot;
+ lfn_checksum = lfn->alias_checksum;
+ lfn_unicode = alloc( (lfn_slot*CHARS_PER_LFN+1)*2 );
+ lfn_offsets = alloc( lfn_slot*sizeof(loff_t) );
+ lfn_parts = 0;
+ break;
}
}
- else if ((lfn->id & LFN_ID_SLOTMASK) != lfn_slot) {
+ else if (slot != lfn_slot) {
/* wrong sequence number */
/* Causes: 1) seq-no destroyed */
/* Fixes: 1) delete LFN, 2) fix number (maybe only if following parts
* are ok?, maybe only if checksum is ok?) (Attention: space
* for name was allocated before!) */
int can_fix = 0;
printf( "Unexpected long filename sequence number "
"(%d vs. expected %d).\n",
- (lfn->id & LFN_ID_SLOTMASK), lfn_slot );
- if (lfn->alias_checksum == lfn_checksum) {
+ slot, lfn_slot );
+ if (lfn->alias_checksum == lfn_checksum && lfn_slot > 0) {
char *part1 = CNV_THIS_PART(lfn);
char *part2 = CNV_PARTS_SO_FAR();
printf( " It could be that just the number is wrong\n"
" if \"%s\" seems to match \"%s\".\n", part1, part2 );
free( part1 );
free( part2 );
can_fix = 1;
}
if (interactive) {
printf( "1: Delete LFN\n2: Leave it as it is (and ignore LFN so far)\n" );
if (can_fix)
printf( "3: Correct sequence number\n" );
}
else printf( " Not auto-correcting this.\n" );
- if (interactive) {
- switch( get_key( can_fix ? "123" : "12", "?" )) {
- case '1':
- lfn_offsets[lfn_parts++] = dir_offset;
- clear_lfn_slots( 0, lfn_parts-1 );
- lfn_reset();
- return;
- case '2':
- lfn_reset();
- return;
- case '3':
- lfn->id = (lfn->id & ~LFN_ID_SLOTMASK) | lfn_slot;
- fs_write( dir_offset+offsetof(LFN_ENT,id),
- sizeof(lfn->id), &lfn->id );
- break;
+ switch( interactive ? get_key( can_fix ? "123" : "12", "?" ) : '2') {
+ case '1':
+ if (!lfn_offsets) {
+ lfn_offsets = alloc( sizeof(loff_t) );
+ lfn_parts = 0;
}
+ lfn_offsets[lfn_parts++] = dir_offset;
+ clear_lfn_slots( 0, lfn_parts-1 );
+ lfn_reset();
+ return;
+ case '2':
+ lfn_reset();
+ return;
+ case '3':
+ lfn->id = (lfn->id & ~LFN_ID_SLOTMASK) | lfn_slot;
+ fs_write( dir_offset+offsetof(LFN_ENT,id),
+ sizeof(lfn->id), &lfn->id );
+ break;
}
}
if (lfn->alias_checksum != lfn_checksum) {
/* checksum mismatch */
/* Causes: 1) checksum field here destroyed */
/* Fixes: 1) delete LFN, 2) fix checksum */
printf( "Checksum in long filename part wrong "
@@ -385,35 +388,33 @@ char *lfn_get( DIR_ENT *de )
long_name, short_name );
free( long_name );
if (interactive) {
printf( "1: Delete LFN\n2: Leave it as it is.\n"
"3: Fix numbering (truncates long name and attaches "
"it to short name %s)\n", short_name );
}
else printf( " Not auto-correcting this.\n" );
- if (interactive) {
- switch( get_key( "123", "?" )) {
- case '1':
- clear_lfn_slots( 0, lfn_parts-1 );
- lfn_reset();
- return NULL;
- case '2':
- lfn_reset();
- return NULL;
- case '3':
- for( i = 0; i < lfn_parts; ++i ) {
- __u8 id = (lfn_parts-i) | (i==0 ? LFN_ID_START : 0);
- fs_write( lfn_offsets[i]+offsetof(LFN_ENT,id),
- sizeof(id), &id );
- }
- memmove( lfn_unicode, lfn_unicode+lfn_slot*CHARS_PER_LFN*2,
- lfn_parts*CHARS_PER_LFN*2 );
- break;
+ switch( interactive ? get_key( "123", "?" ) : '2') {
+ case '1':
+ clear_lfn_slots( 0, lfn_parts-1 );
+ lfn_reset();
+ return NULL;
+ case '2':
+ lfn_reset();
+ return NULL;
+ case '3':
+ for( i = 0; i < lfn_parts; ++i ) {
+ __u8 id = (lfn_parts-i) | (i==0 ? LFN_ID_START : 0);
+ fs_write( lfn_offsets[i]+offsetof(LFN_ENT,id),
+ sizeof(id), &id );
}
+ memmove( lfn_unicode, lfn_unicode+lfn_slot*CHARS_PER_LFN*2,
+ lfn_parts*CHARS_PER_LFN*2 );
+ break;
}
}
for (sum = 0, i = 0; i < 11; i++)
sum = (((sum&1) << 7) | ((sum&0xfe) >> 1)) + de->name[i];
if (sum != lfn_checksum) {
/* checksum doesn't match, long name doesn't apply to this alias */
/* Causes: 1) alias renamed */