compiler: Validate initializer extents in checked code

442ab96cb5a291b79efbd7069e5b511a4fb873f0c477b5f3b61f824ed7f01504
Alexis Sellier committed ago 1 parent 8d09b8cb
lib/std/lang/il/binary/decodeTests.rad +44 -0
288 288
    try rejected(&package, LIMITS);
289 289
    set package.symbols = &["p", ""];
290 290
    try rejected(&package, LIMITS);
291 291
}
292 292
293 +
/// Initializer extents include every item kind and cumulative size boundaries.
294 +
@test unsafe fn initializerExtents() throws (testing::TestError) {
295 +
    for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] {
296 +
        try checkInitializerExtents(il::DataItem::Val { typ, val: 0 }, il::typeSize(typ));
297 +
    }
298 +
    try checkInitializerExtents(il::DataItem::Sym("p"), 8);
299 +
    try checkInitializerExtents(il::DataItem::Fn("p"), 8);
300 +
    try checkInitializerExtents(il::DataItem::Str("abc"), 3);
301 +
    try checkInitializerExtents(il::DataItem::Str(""), 0);
302 +
    try checkInitializerExtents(il::DataItem::Undef, 1);
303 +
}
304 +
305 +
/// Test one repeated item followed by a byte at exact-fit and shorter sizes.
306 +
unsafe fn checkInitializerExtents(item: il::DataItem, width: u32) throws (testing::TestError) {
307 +
    /// Stable initialized values retained by the fixture's data definition.
308 +
    static values: [il::DataValue; 2] = [il::DataValue { item: il::DataItem::Undef, count: 1 }; 2];
309 +
    for count in [0 as u32, 1, 3, 0xffffffff] {
310 +
        set values[0] = il::DataValue { item, count };
311 +
        let extent = width as u64 * count as u64 + 1;
312 +
        let size = extent as u32 if extent <= 0xffffffff else 0xffffffff;
313 +
        for declared in [size, size - 1] {
314 +
            let definition = il::Data {
315 +
                name: "p", size: declared, alignment: 1, readOnly: false, isZeroInit: false,
316 +
                values: &values[..],
317 +
            };
318 +
            let package = binary::Package {
319 +
                symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
320 +
                program: il::Program { data: retainData(&[definition]), fns: &[] },
321 +
            };
322 +
            let mut bytes: [u8; 256] = [0; 256];
323 +
            let length = try program::encode(&mut bytes[..], &package) catch { throw testing::TestError::Failed; };
324 +
            let mut arena = alloc::new(&mut MEMORY[..]);
325 +
            set arena.offset = 8;
326 +
            let mut failed = false;
327 +
            try program::decode(&bytes[..length], &mut arena, LIMITS) catch err {
328 +
                assert err == binary::Error::Invalid;
329 +
                assert arena.offset == 8;
330 +
                set failed = true;
331 +
            };
332 +
            assert failed == (extent > declared as u64);
333 +
        }
334 +
    }
335 +
}
336 +
293 337
/// Check function block limits, parameter indices, and predecessor indices.
294 338
@test unsafe fn invalidFunctions() throws (testing::TestError) {
295 339
    let mut instrs = [il::Instr::Ret { val: nil }];
296 340
    let mut block = il::Block {
297 341
        label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[], loopDepth: 0,
lib/std/lang/il/binary/program.rad +25 -19
113 113
        set params[i] = il::Param { value, type: t };
114 114
    }
115 115
    return (&params[..]) as *unsafe [il::Param];
116 116
}
117 117
118 +
/// Extend an initializer extent without exceeding its declared storage.
119 +
fn extendDataExtent(extent: u64, value: il::DataValue, size: u32) -> u64 throws (binary::Error) {
120 +
    let mut width: u32 = 0;
121 +
    match value.item {
122 +
        case il::DataItem::Val { typ, .. } => {
123 +
            set width = il::typeSize(typ);
124 +
        },
125 +
        case il::DataItem::Sym(_), il::DataItem::Fn(_) => {
126 +
            set width = 8;
127 +
        },
128 +
        case il::DataItem::Str(text) => {
129 +
            set width = text.len;
130 +
        },
131 +
        case il::DataItem::Undef => {
132 +
            set width = 1;
133 +
        },
134 +
    }
135 +
    let amount = width as u64 * value.count as u64;
136 +
    if extent > size as u64 or amount > size as u64 - extent {
137 +
        throw binary::Error::Invalid;
138 +
    }
139 +
    return extent + amount;
140 +
}
141 +
118 142
/// Read global data and check initializer extents against declared storage.
119 143
unsafe fn readData 'input (input: &mut reader::Reader 'input) -> *[il::Data] throws (binary::Error) {
120 144
    let n = try reader::count(input, 18);
121 145
    let items = try reader::storage(input, @sizeOf(il::Data), @alignOf(il::Data), n)
122 146
        as *mut [il::Data];
133 157
        let values = try reader::storage(input, @sizeOf(il::DataValue), @alignOf(il::DataValue), count)
134 158
            as *mut [il::DataValue];
135 159
        let mut extent: u64 = 0;
136 160
        for j in 0..count {
137 161
            let value = try reader::dataValue(input);
138 -
            let mut width: u32 = 0;
139 -
            match value.item {
140 -
                case il::DataItem::Val { typ, .. } => {
141 -
                    set width = il::typeSize(typ);
142 -
                },
143 -
                case il::DataItem::Sym(_), il::DataItem::Fn(_) => {
144 -
                    set width = 8;
145 -
                },
146 -
                case il::DataItem::Str(text) => {
147 -
                    set width = text.len;
148 -
                },
149 -
                case il::DataItem::Undef => {
150 -
                    set width = 1;
151 -
                },
152 -
            }
153 -
            set extent += width as u64 * value.count as u64;
154 -
            if extent > size as u64 {
155 -
                throw binary::Error::Invalid;
156 -
            }
162 +
            set extent = try extendDataExtent(extent, value, size);
157 163
            set values[j] = value;
158 164
        }
159 165
        set items[i] = il::Data { name, size, alignment, readOnly, isZeroInit, values };
160 166
    }
161 167
    return items;