DF-3035 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | --- a/sys/vfs/ufs/ffs_alloc.c +++ b/sys/vfs/ufs/ffs_alloc.c @@ -80,6 +80,7 @@ static ino_t ffs_nodealloccg (struct inode *, int, ufs_daddr_t, int); static ufs_daddr_t ffs_mapsearch (struct fs *, struct cg *, ufs_daddr_t, int); +static int ffs_chkcg (struct fs *, struct cg *, int); /* * Allocate a block in the filesystem. @@ -872,6 +873,16 @@ fs = ip->i_fs; /* + * The preferred cg derives from on-disk data (block preferences + * come from inode block pointers) and is used by the allocators + * to index fs_csp[] (fs_cs()) before anything else; clamp it. + */ + if (cg < 0 || cg >= fs->fs_ncg) { + cg = ino_to_cg(fs, ip->i_number); + if (cg < 0 || cg >= fs->fs_ncg) + cg = 0; + } + /* * 1: preferred cylinder group */ result = (*allocator)(ip, cg, pref, size); @@ -943,6 +954,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } cgp->cg_time = time_second; bno = dtogd(fs, bprev); blksfree = cg_blksfree(cgp); @@ -1021,6 +1036,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } cgp->cg_time = time_second; if (size == fs->fs_bsize) { bno = ffs_alloccgblk(ip, bp, bpref); @@ -1249,6 +1268,8 @@ cgp = (struct cg *)bp->b_data; if (!cg_chkmagic(cgp)) goto fail; + if (!ffs_chkcg(fs, cgp, cg)) + goto fail; /* * Check to see if a cluster of the needed size (or bigger) is @@ -1380,6 +1401,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } inosused = cg_inosused(cgp); icheckmiss = 0; @@ -1527,6 +1552,10 @@ brelse(bp); return; } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return; + } cgp->cg_time = time_second; bno = dtogd(fs, bno); blksfree = cg_blksfree(cgp); @@ -1787,6 +1816,10 @@ brelse(bp); return (0); } + if (!ffs_chkcg(fs, cgp, cg)) { + brelse(bp); + return (0); + } cgp->cg_time = time_second; inosused = cg_inosused(cgp); ino %= fs->fs_ipg; @@ -1876,6 +1909,65 @@ } /* + * Validate an on-disk cylinder group header before any of its fields are + * used as array indexes or byte offsets into the cg buffer. Previously + * only cg_chkmagic() (a magic comparison) was applied, so a crafted + * filesystem image could turn cg_freeoff/cg_iusedoff/cg_boff/cg_btotoff/ + * cg_clustersumoff/cg_clusteroff, cg_rotor/cg_frotor/cg_irotor, cg_cgx, + * or cg_nclusterblks into out-of-bounds kernel reads and writes. + * + * Historic 4.2BSD ocg-format groups keep all maps inline in the header + * (no offsets), so they are accepted as before. + */ +static int +ffs_chkcg(struct fs *fs, struct cg *cgp, int cg) +{ + int64_t end; + + if (cgp->cg_magic != CG_MAGIC) /* historic ocg layout */ + return (1); + if (cgp->cg_cgx != cg) + return (0); + if (cgp->cg_rotor < 0 || cgp->cg_rotor >= fs->fs_fpg || + cgp->cg_frotor < 0 || cgp->cg_frotor >= fs->fs_fpg || + cgp->cg_irotor < 0 || cgp->cg_irotor >= fs->fs_ipg) + return (0); + if (cgp->cg_btotoff < offsetof(struct cg, cg_space) || + cgp->cg_boff < cgp->cg_btotoff || + cgp->cg_iusedoff < cgp->cg_boff || + cgp->cg_freeoff < cgp->cg_iusedoff) + return (0); + end = (int64_t)cgp->cg_btotoff + fs->fs_cpg * sizeof(int32_t); + if (end > cgp->cg_boff) + return (0); + end = (int64_t)cgp->cg_boff + fs->fs_cpg * fs->fs_nrpos * sizeof(int16_t); + if (end > cgp->cg_iusedoff) + return (0); + end = (int64_t)cgp->cg_iusedoff + howmany(fs->fs_ipg, NBBY); + if (end > cgp->cg_freeoff) + return (0); + end = (int64_t)cgp->cg_freeoff + howmany(fs->fs_fpg, NBBY); + if (end > fs->fs_cgsize) + return (0); + if (fs->fs_contigsumsize > 0) { + if (cgp->cg_nclusterblks < 0 || + cgp->cg_nclusterblks > fragstoblks(fs, fs->fs_fpg)) + return (0); + if (cgp->cg_clusteroff < cgp->cg_clustersumoff) + return (0); + end = (int64_t)cgp->cg_clustersumoff + + (fs->fs_contigsumsize + 1) * sizeof(int32_t); + if (end > cgp->cg_clusteroff) + return (0); + end = (int64_t)cgp->cg_clusteroff + + howmany(cgp->cg_nclusterblks, NBBY); + if (end > fs->fs_cgsize) + return (0); + } + return (1); +} + +/* * Update the cluster map because of an allocation or free. * * Cnt == 1 means free; cnt == -1 means allocating. |