Mercurial > illumos > illumos-gate
changeset 494:e082e44c7fce
6244328 hsfs not resistant against malformed ISO9660 filesystems, crashes/hangs
author | frankho |
---|---|
date | Wed, 07 Sep 2005 05:57:46 -0700 |
parents | d2df5159c731 |
children | 310ccf2a1604 |
files | usr/src/uts/common/fs/hsfs/hsfs_node.c usr/src/uts/common/fs/hsfs/hsfs_rrip.c usr/src/uts/common/fs/hsfs/hsfs_susp_subr.c usr/src/uts/common/fs/hsfs/hsfs_vfsops.c usr/src/uts/common/fs/hsfs/hsfs_vnops.c |
diffstat | 5 files changed, 162 insertions(+), 139 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/uts/common/fs/hsfs/hsfs_node.c Wed Sep 07 04:40:39 2005 -0700 +++ b/usr/src/uts/common/fs/hsfs/hsfs_node.c Wed Sep 07 05:57:46 2005 -0700 @@ -20,7 +20,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -45,6 +45,7 @@ #include <sys/fbuf.h> #include <sys/kmem.h> #include <sys/policy.h> +#include <sys/sunddi.h> #include <vm/hat.h> #include <vm/as.h> #include <vm/pvn.h> @@ -744,7 +745,7 @@ * fold it to upper case. */ if (is_rrip) { - (void) strcpy(cmpname, name); + (void) strlcpy(cmpname, name, cmpname_size); cmpnamelen = namlen; } else { /* @@ -776,25 +777,18 @@ tryagain: while (offset < end) { - - if ((offset & MAXBMASK) + MAXBSIZE > dirsiz) - bytes_wanted = dirsiz - (offset & MAXBMASK); - else - bytes_wanted = MAXBSIZE; + bytes_wanted = MIN(MAXBSIZE, dirsiz - (offset & MAXBMASK)); error = fbread(dvp, (offset_t)(offset & MAXBMASK), (unsigned int)bytes_wanted, S_READ, &fbp); if (error) goto done; - last_offset = (offset & MAXBMASK) + fbp->fb_count - 1; - -#define rel_offset(offset) ((offset) & MAXBOFFSET) /* index into cur blk */ + last_offset = (offset & MAXBMASK) + fbp->fb_count; - switch (process_dirblock(fbp, &offset, - last_offset, cmpname, - cmpnamelen, fsp, dhp, dvp, - vpp, &error, is_rrip)) { + switch (process_dirblock(fbp, &offset, last_offset, + cmpname, cmpnamelen, fsp, dhp, dvp, vpp, &error, + is_rrip)) { case FOUND_ENTRY: /* found an entry, either correct or not */ goto done; @@ -896,7 +890,7 @@ hdp->nlink = 2; } else { hs_log_bogus_disk_warning(fsp, - HSFS_ERR_UNSUP_TYPE, flags); + HSFS_ERR_UNSUP_TYPE, flags); return (EINVAL); } hdp->uid = fsp -> hsfs_vol.vol_uid; @@ -923,7 +917,7 @@ hdp->nlink = 2; } else { hs_log_bogus_disk_warning(fsp, - HSFS_ERR_UNSUP_TYPE, flags); + HSFS_ERR_UNSUP_TYPE, flags); return (EINVAL); } hdp->uid = fsp -> hsfs_vol.vol_uid; @@ -987,14 +981,18 @@ if (NAME_HAS_CHANGED(name_change_flag)) return (0); - /* return the pointer to the directory name and its length */ + /* + * Fall back to the ISO name. Note that as in process_dirblock, + * the on-disk filename length must be validated against ISO + * limits - which, in case of RR present but no RR name found, + * are NOT identical to fsp->hsfs_namemax on this filesystem. + */ on_disk_name = (char *)HDE_name(dirp); on_disk_namelen = (int)HDE_NAME_LEN(dirp); - if (((int)(fsp->hsfs_namemax) > 0) && - (on_disk_namelen > (int)(fsp->hsfs_namemax))) { + if (on_disk_namelen > ISO_FILE_NAMELEN) { hs_log_bogus_disk_warning(fsp, HSFS_ERR_BAD_FILE_LEN, 0); - on_disk_namelen = fsp->hsfs_namemax; + on_disk_namelen = ISO_FILE_NAMELEN; } if (dnp != NULL) { namelen = hs_namecopy(on_disk_name, dnp, on_disk_namelen, @@ -1180,15 +1178,17 @@ int res; int parsedir_res; size_t rrip_name_size; - int rr_namelen; + int rr_namelen = 0; char *rrip_name_str = NULL; char *rrip_tmp_name = NULL; enum dirblock_result err = 0; int did_fbrelse = 0; char uppercase_name[ISO_FILE_NAMELEN]; - /* return after performing cleanup-on-exit */ -#define PD_return(retval) { err = retval; goto do_ret; } +#define PD_return(retval) \ + { err = retval; goto do_ret; } /* return after cleanup */ +#define rel_offset(offset) \ + ((offset) & MAXBOFFSET) /* index into cur blk */ if (is_rrip) { rrip_name_size = RRIP_FILE_NAMELEN + 1; @@ -1202,39 +1202,61 @@ /* * Directory Entries cannot span sectors. - * Unused bytes at the end of each sector are zeroed. - * Therefore, detect this condition when the size - * field of the directory entry is zero. + * + * Unused bytes at the end of each sector are zeroed + * according to ISO9660, but we cannot rely on this + * since both media failures and maliciously corrupted + * media may return arbitrary values. + * We therefore have to check for consistency: + * The size of a directory entry must be at least + * 34 bytes (the size of the directory entry metadata), + * or zero (indicating the end-of-sector condition). + * For a non-zero directory entry size of less than + * 34 Bytes, log a warning. + * In any case, skip the rest of this sector and + * continue with the next. */ hdlen = (int)((uchar_t) - HDE_DIR_LEN(&blkp[rel_offset(*offset)])); - if (hdlen == 0) { - /* advance to next sector boundary */ - *offset = (*offset & MAXHSMASK) + HS_SECTOR_SIZE; + HDE_DIR_LEN(&blkp[rel_offset(*offset)])); - if (*offset > last_offset) { - /* end of block */ - PD_return(HIT_END) - } else - continue; + if (hdlen < HDE_ROOT_DIR_REC_SIZE || + *offset + hdlen > last_offset) { + /* + * Advance to the next sector boundary + */ + *offset = roundup(*offset + 1, HS_SECTOR_SIZE); + if (hdlen) + hs_log_bogus_disk_warning(fsp, + HSFS_ERR_TRAILING_JUNK, 0); + continue; } - /* - * Zero out the hd to read new directory - */ bzero(&hd, sizeof (hd)); /* - * Just ignore invalid directory entries. - * XXX - maybe hs_parsedir() will detect EXISTENCE bit + * Check the filename length in the ISO record for + * plausibility and reset it to a safe value, in case + * the name length byte is out of range. Since the ISO + * name will be used as fallback if the rockridge name + * is invalid/nonexistant, we must make sure not to + * blow the bounds and initialize dnamelen to a sensible + * value within the limits of ISO9660. + * In addition to that, the ISO filename is part of the + * directory entry. If the filename length is too large + * to fit, the record is invalid and we'll advance to + * the next. */ dirp = &blkp[rel_offset(*offset)]; dname = (char *)HDE_name(dirp); dnamelen = (int)((uchar_t)HDE_NAME_LEN(dirp)); - if (dnamelen > (int)(fsp->hsfs_namemax)) { + if (dnamelen > hdlen - HDE_FDESIZE) { hs_log_bogus_disk_warning(fsp, - HSFS_ERR_BAD_FILE_LEN, 0); - dnamelen = fsp->hsfs_namemax; + HSFS_ERR_BAD_FILE_LEN, 0); + goto skip_rec; + } else if (dnamelen > ISO_FILE_NAMELEN) { + hs_log_bogus_disk_warning(fsp, + HSFS_ERR_BAD_FILE_LEN, 0); + dnamelen = ISO_FILE_NAMELEN; } /* @@ -1280,15 +1302,17 @@ else dnamelen = strip_trailing(fsp, dname, dnamelen); + ASSERT(dnamelen <= ISO_FILE_NAMELEN); + if (uppercase_cp(dname, uppercase_name, dnamelen)) hs_log_bogus_disk_warning(fsp, - HSFS_ERR_LOWER_CASE_NM, 0); + HSFS_ERR_LOWER_CASE_NM, 0); dname = uppercase_name; - if (! is_rrip && - (fsp->hsfs_flags & HSFSMNT_NOTRAILDOT) && - dname[ dnamelen-1 ] == '.' && - CAN_TRUNCATE_DOT(dname, dnamelen)) - dname[ --dnamelen ] = '\0'; + if (!is_rrip && + (fsp->hsfs_flags & HSFSMNT_NOTRAILDOT) && + dname[dnamelen - 1] == '.' && + CAN_TRUNCATE_DOT(dname, dnamelen)) + dname[--dnamelen] = '\0'; } /* @@ -1298,28 +1322,23 @@ /* if we saw a lower case name we can't do this test either */ if (strict_iso9660_ordering && !is_rrip && - !HSFS_HAVE_LOWER_CASE(fsp) && *nm < *dname) { + !HSFS_HAVE_LOWER_CASE(fsp) && *nm < *dname) { RESTORE_NM(rrip_tmp_name, nm); PD_return(WENT_PAST) } - if (*nm != *dname || nmlen != dnamelen) { - /* look at next dir entry */ - RESTORE_NM(rrip_tmp_name, nm); - *offset += hdlen; - continue; - } + if (*nm != *dname || nmlen != dnamelen) + goto skip_rec; if ((res = nmcmp(dname, nm, nmlen, is_rrip)) == 0) { /* name matches */ - parsedir_res = - hs_parsedir(fsp, dirp, &hd, (char *)NULL, - (int *)NULL); + parsedir_res = hs_parsedir(fsp, dirp, &hd, + (char *)NULL, (int *)NULL); if (!parsedir_res) { uint_t lbn; /* logical block number */ lbn = dhp->hs_dirent.ext_lbn + - dhp->hs_dirent.xar_len; + dhp->hs_dirent.xar_len; /* * Need to do an fbrelse() on the buffer, * as hs_makenode() may try to acquire @@ -1328,8 +1347,8 @@ */ fbrelse(fbp, S_READ); did_fbrelse = 1; - *vpp = hs_makenode(&hd, lbn, - *offset, dvp->v_vfsp); + *vpp = hs_makenode(&hd, lbn, *offset, + dvp->v_vfsp); if (*vpp == NULL) { *error = ENFILE; RESTORE_NM(rrip_tmp_name, nm); @@ -1355,6 +1374,7 @@ * name > dir entry, * look at next one. */ +skip_rec: *offset += hdlen; RESTORE_NM(rrip_tmp_name, nm); } @@ -1365,7 +1385,7 @@ kmem_free(rrip_name_str, rrip_name_size); if (rrip_tmp_name) kmem_free(rrip_tmp_name, rrip_name_size); - if (! did_fbrelse) + if (!did_fbrelse) fbrelse(fbp, S_READ); return (err); #undef PD_return
--- a/usr/src/uts/common/fs/hsfs/hsfs_rrip.c Wed Sep 07 04:40:39 2005 -0700 +++ b/usr/src/uts/common/fs/hsfs/hsfs_rrip.c Wed Sep 07 05:57:46 2005 -0700 @@ -20,8 +20,8 @@ * CDDL HEADER END */ /* - * Copyright (c) 1990,1997,2000,2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" @@ -51,6 +51,7 @@ #include <sys/mode.h> #include <sys/mkdev.h> #include <sys/ddi.h> +#include <sys/sunddi.h> #include <vm/page.h> @@ -206,18 +207,14 @@ int rrip_flags, /* component/name flag */ uchar_t *SUA_string, /* string from SUA */ size_t SUA_string_len, /* length of SUA string */ - uchar_t *to_string, /* string to copy to */ - int *to_string_len_p, /* ptr to cur. str len */ + uchar_t *dst, /* string to copy to */ + int *dst_lenp, /* ptr to cur. str len */ ulong_t *name_flags_p, /* internal name flags */ - int max_name_len) /* limit dest string to */ + int dst_size) /* limit dest string to */ /* this value */ { - size_t tmp_name_len; - - if (IS_NAME_BIT_SET(rrip_flags, RRIP_NAME_ROOT)) { - (void) strcpy((char *)to_string, "/"); - *to_string_len_p = 1; - } + if (IS_NAME_BIT_SET(rrip_flags, RRIP_NAME_ROOT)) + (void) strcpy((char *)dst, "/"); if (IS_NAME_BIT_SET(rrip_flags, RRIP_NAME_CURRENT)) { SUA_string = (uchar_t *)"."; @@ -243,21 +240,22 @@ } /* - * Remember we must strncpy to the end of the curent string - * name because there might be something there. Also, don't - * go past the max_name_len system boundry. + * Since SUA_string isn't NULL terminated, we use strlcat() + * to add the trailing NULL byte. Defensive programming, don't + * ever overwrite beyond the end of the destination buffer, + * and neither attempt to read beyond the end of SUA_string. + * We assume the caller properly validates SUA_string_len as + * we have no way of doing so. + * Note for corrupted filesystems, SUA_string may contain + * NULL bytes. If that happens, the destination string length + * returned will be larger than the "actual" length, since + * strlcat() terminates when encountering the NULL byte. + * This causes no harm (apart from filenames not being reported + * 'correctly', but then what is correct on a corrupted fs ?) + * so we don't bother assigning strlen(dst) to *dst_lenp. */ - tmp_name_len = strlen((char *)to_string); - - SUA_string_len = (tmp_name_len + SUA_string_len) > max_name_len ? - (max_name_len - tmp_name_len) : (SUA_string_len); - - (void) strncpy((char *)(to_string + tmp_name_len), - (char *)SUA_string, SUA_string_len); - - /* NULL terminate string */ - *(to_string + tmp_name_len + SUA_string_len) = '\0'; - *(to_string_len_p) += (int)SUA_string_len; + *dst_lenp = MIN(dst_size, strlen((char *)dst) + SUA_string_len + 1); + (void) strlcat((char *)dst, (char *)SUA_string, (*dst_lenp)--); if (IS_NAME_BIT_SET(rrip_flags, RRIP_NAME_CONTINUE)) SET_NAME_BIT(*(name_flags_p), RRIP_NAME_CONTINUE);
--- a/usr/src/uts/common/fs/hsfs/hsfs_susp_subr.c Wed Sep 07 04:40:39 2005 -0700 +++ b/usr/src/uts/common/fs/hsfs/hsfs_susp_subr.c Wed Sep 07 05:57:46 2005 -0700 @@ -22,8 +22,8 @@ /* * System Use Sharing protocol subroutines for High Sierra filesystem * - * Copyright (c) 1990,2000,2001 by Sun Microsystems, Inc. - * All rights reserved. + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" @@ -183,8 +183,8 @@ /* * parse_signatures() * - * for the signature string that is passed to this function, find the - * correct handling function. + * Find the correct handling function for the signature string that is + * passed to this function. * * signature searching: * @@ -302,18 +302,19 @@ } /* for extnp (extension parsing) */ /* - * Opps, did not find this signature, so we must - * advance on the the next signature in the SUA and - * hope to god that it is in the susp format, or we get - * hosed. + * Opps, did not find this signature. We must + * advance on the the next signature in the SUA + * and pray to persumedly omniscient, omnipresent, + * almighty transcendental being(s) that the next + * record is in the susp format, or we get hosed. */ if (SUA_rem < SUF_MIN_LEN) return (END_OF_SUA); - SUA_rem -= SUF_LEN(sig_string); sig_string += SUF_LEN(sig_string); +next_signature: /* * Failsafe */ @@ -321,21 +322,6 @@ return (END_OF_SUA); } - continue; - -next_signature: - - /* - * we just want to continue in this for loop, not - * the innermost one. Unfortunately, goto is - * the only way, until we get a break that - * takes an argument. - * - * This continue statement is not really needed, but - * the compiler barfs if it is not here. - */ - continue; - } /* while */ return (END_OF_SUA); @@ -451,6 +437,17 @@ int error; uint_t secno; + /* + * Guard against invalid continuation area records. + * Both cont_offset and cont_len must be no longer than + * HS_SECTOR_SIZE. If they are, return an error. + */ + if (cont_info_p->cont_offset > HS_SECTOR_SIZE || + cont_info_p->cont_len > HS_SECTOR_SIZE) { + cmn_err(CE_NOTE, "get_cont_area: invalid offset/length"); + return (1); + } + if (*buf_pp == (uchar_t *)NULL) *buf_pp = kmem_alloc((size_t)HS_SECTOR_SIZE, KM_SLEEP);
--- a/usr/src/uts/common/fs/hsfs/hsfs_vfsops.c Wed Sep 07 04:40:39 2005 -0700 +++ b/usr/src/uts/common/fs/hsfs/hsfs_vfsops.c Wed Sep 07 05:57:46 2005 -0700 @@ -20,7 +20,7 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -466,22 +466,31 @@ struct buf *bp; int error; int fsid; - int size = CHECKSUM_SIZE; secno = hsvp->root_dir.ext_lbn >> hsvp->lbn_secshift; - bp = bread(devvp->v_rdev, secno * 4, size); + bp = bread(devvp->v_rdev, secno * 4, CHECKSUM_SIZE); error = geterror(bp); - if (!error) { + + /* + * An error on read or a partial read means we asked + * for a nonexistant/corrupted piece of the device + * (including past-the-end of the media). Don't + * try to use the checksumming method then. + */ + if (!error && bp->b_bcount == CHECKSUM_SIZE) { int *ibuf = (int *)bp->b_un.b_addr; - int isize = size / sizeof (int); int i; fsid = 0; - for (i = 0; i < isize; i++) + for (i = 0; i < CHECKSUM_SIZE / sizeof (int); i++) fsid ^= ibuf[ i ]; - } else /* use creation date */ + } else { + /* + * Fallback - use creation date + */ fsid = hsvp->cre_date.tv_sec; + } brelse(bp); @@ -626,6 +635,7 @@ if (hs_remakenode(fsp->hsfs_vol.root_dir.ext_lbn, (uint_t)0, vfsp, &fsp->hsfs_rootvp)) { error = EINVAL; + mutex_exit(&hs_mounttab_lock); goto cleanup; } } else {
--- a/usr/src/uts/common/fs/hsfs/hsfs_vnops.c Wed Sep 07 04:40:39 2005 -0700 +++ b/usr/src/uts/common/fs/hsfs/hsfs_vnops.c Wed Sep 07 05:57:46 2005 -0700 @@ -402,10 +402,7 @@ nd = (struct dirent64 *)outbuf; while (offset < dirsiz) { - if ((offset & MAXBMASK) + MAXBSIZE > dirsiz) - bytes_wanted = dirsiz - (offset & MAXBMASK); - else - bytes_wanted = MAXBSIZE; + bytes_wanted = MIN(MAXBSIZE, dirsiz - (offset & MAXBMASK)); error = fbread(vp, (offset_t)(offset & MAXBMASK), (unsigned int)bytes_wanted, S_READ, &fbp); @@ -413,33 +410,35 @@ goto done; blkp = (uchar_t *)fbp->fb_addr; - last_offset = (offset & MAXBMASK) + fbp->fb_count - 1; + last_offset = (offset & MAXBMASK) + fbp->fb_count; #define rel_offset(offset) ((offset) & MAXBOFFSET) /* index into blkp */ while (offset < last_offset) { /* - * Directory Entries cannot span sectors. - * Unused bytes at the end of each sector are zeroed. - * Therefore, detect this condition when the size - * field of the directory entry is zero. + * Very similar validation code is found in + * process_dirblock(), hsfs_node.c. + * For an explanation, see there. + * It may make sense for the future to + * "consolidate" the code in hs_parsedir(), + * process_dirblock() and hsfs_readdir() into + * a single utility function. */ hdlen = (int)((uchar_t) HDE_DIR_LEN(&blkp[rel_offset(offset)])); - if (hdlen == 0) { - /* advance to next sector boundary */ - offset = (offset & MAXHSMASK) + HS_SECTOR_SIZE; - + if (hdlen < HDE_ROOT_DIR_REC_SIZE || + offset + hdlen > last_offset) { /* - * Have we reached the end of current block? + * advance to next sector boundary */ - if (offset > last_offset) - break; - else - continue; + offset = roundup(offset + 1, HS_SECTOR_SIZE); + if (hdlen) + hs_log_bogus_disk_warning(fsp, + HSFS_ERR_TRAILING_JUNK, 0); + + continue; } - /* make sure this is nullified before reading it */ bzero(&hd, sizeof (hd)); /* @@ -509,7 +508,6 @@ hd.sym_link = (char *)NULL; } } - offset += hdlen; } fbrelse(fbp, S_READ);