Skip to content

Commit 92b4ae9

Browse files
committed
std: Fix overflow of HashMap's capacity
1 parent 3db1fec commit 92b4ae9

File tree

2 files changed

+70
-32
lines changed

2 files changed

+70
-32
lines changed

src/libstd/collections/hashmap/table.rs

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -538,32 +538,45 @@ fn test_rounding() {
538538
assert_eq!(round_up_to_next(5, 4), 8);
539539
}
540540

541-
// Returns a tuple of (minimum required malloc alignment, hash_offset,
542-
// key_offset, val_offset, array_size), from the start of a mallocated array.
543-
fn calculate_offsets(
544-
hash_size: uint, hash_align: uint,
545-
keys_size: uint, keys_align: uint,
546-
vals_size: uint, vals_align: uint) -> (uint, uint, uint, uint, uint) {
541+
// Returns a tuple of (key_offset, val_offset),
542+
// from the start of a mallocated array.
543+
fn calculate_offsets(hashes_size: uint,
544+
keys_size: uint, keys_align: uint,
545+
vals_align: uint)
546+
-> (uint, uint) {
547+
let keys_offset = round_up_to_next(hashes_size, keys_align);
548+
let end_of_keys = keys_offset + keys_size;
547549

548-
let hash_offset = 0;
549-
let end_of_hashes = hash_offset + hash_size;
550+
let vals_offset = round_up_to_next(end_of_keys, vals_align);
550551

551-
let keys_offset = round_up_to_next(end_of_hashes, keys_align);
552-
let end_of_keys = keys_offset + keys_size;
552+
(keys_offset, vals_offset)
553+
}
553554

554-
let vals_offset = round_up_to_next(end_of_keys, vals_align);
555-
let end_of_vals = vals_offset + vals_size;
555+
// Returns a tuple of (minimum required malloc alignment, hash_offset,
556+
// array_size), from the start of a mallocated array.
557+
fn calculate_allocation(hash_size: uint, hash_align: uint,
558+
keys_size: uint, keys_align: uint,
559+
vals_size: uint, vals_align: uint)
560+
-> (uint, uint, uint) {
561+
let hash_offset = 0;
562+
let (_, vals_offset) = calculate_offsets(hash_size,
563+
keys_size, keys_align,
564+
vals_align);
565+
let end_of_vals = vals_offset + vals_size;
556566

557567
let min_align = cmp::max(hash_align, cmp::max(keys_align, vals_align));
558568

559-
(min_align, hash_offset, keys_offset, vals_offset, end_of_vals)
569+
(min_align, hash_offset, end_of_vals)
560570
}
561571

562572
#[test]
563573
fn test_offset_calculation() {
564-
assert_eq!(calculate_offsets(128, 8, 15, 1, 4, 4 ), (8, 0, 128, 144, 148));
565-
assert_eq!(calculate_offsets(3, 1, 2, 1, 1, 1 ), (1, 0, 3, 5, 6));
566-
assert_eq!(calculate_offsets(6, 2, 12, 4, 24, 8), (8, 0, 8, 24, 48));
574+
assert_eq!(calculate_allocation(128, 8, 15, 1, 4, 4), (8, 0, 148));
575+
assert_eq!(calculate_allocation(3, 1, 2, 1, 1, 1), (1, 0, 6));
576+
assert_eq!(calculate_allocation(6, 2, 12, 4, 24, 8), (8, 0, 48));
577+
assert_eq!(calculate_offsets(128, 15, 1, 4), (128, 144));
578+
assert_eq!(calculate_offsets(3, 2, 1, 1), (3, 5));
579+
assert_eq!(calculate_offsets(6, 12, 4, 8), (8, 24));
567580
}
568581

569582
impl<K, V> RawTable<K, V> {
@@ -577,12 +590,11 @@ impl<K, V> RawTable<K, V> {
577590
hashes: 0 as *mut u64,
578591
};
579592
}
580-
let hashes_size = capacity.checked_mul(&size_of::<u64>())
581-
.expect("capacity overflow");
582-
let keys_size = capacity.checked_mul(&size_of::< K >())
583-
.expect("capacity overflow");
584-
let vals_size = capacity.checked_mul(&size_of::< V >())
585-
.expect("capacity overflow");
593+
// No need for `checked_mul` before a more restrictive check performed
594+
// later in this method.
595+
let hashes_size = capacity * size_of::<u64>();
596+
let keys_size = capacity * size_of::< K >();
597+
let vals_size = capacity * size_of::< V >();
586598

587599
// Allocating hashmaps is a little tricky. We need to allocate three
588600
// arrays, but since we know their sizes and alignments up front,
@@ -592,12 +604,19 @@ impl<K, V> RawTable<K, V> {
592604
// This is great in theory, but in practice getting the alignment
593605
// right is a little subtle. Therefore, calculating offsets has been
594606
// factored out into a different function.
595-
let (malloc_alignment, hash_offset, _, _, size) =
596-
calculate_offsets(
607+
let (malloc_alignment, hash_offset, size) =
608+
calculate_allocation(
597609
hashes_size, min_align_of::<u64>(),
598610
keys_size, min_align_of::< K >(),
599611
vals_size, min_align_of::< V >());
600612

613+
// One check for overflow that covers calculation and rounding of size.
614+
let size_of_bucket = size_of::<u64>().checked_add(&size_of::<K>()).unwrap()
615+
.checked_add(&size_of::<V>()).unwrap();
616+
assert!(size >= capacity.checked_mul(&size_of_bucket)
617+
.expect("capacity overflow"),
618+
"capacity overflow");
619+
601620
let buffer = allocate(size, malloc_alignment);
602621

603622
let hashes = buffer.offset(hash_offset as int) as *mut u64;
@@ -613,12 +632,10 @@ impl<K, V> RawTable<K, V> {
613632
let hashes_size = self.capacity * size_of::<u64>();
614633
let keys_size = self.capacity * size_of::<K>();
615634

616-
let keys_offset = (hashes_size + min_align_of::<K>() - 1) & !(min_align_of::<K>() - 1);
617-
let end_of_keys = keys_offset + keys_size;
618-
619-
let vals_offset = (end_of_keys + min_align_of::<V>() - 1) & !(min_align_of::<V>() - 1);
620-
621635
let buffer = self.hashes as *mut u8;
636+
let (keys_offset, vals_offset) = calculate_offsets(hashes_size,
637+
keys_size, min_align_of::<K>(),
638+
min_align_of::<V>());
622639

623640
unsafe {
624641
RawBucket {
@@ -892,9 +909,9 @@ impl<K, V> Drop for RawTable<K, V> {
892909
let hashes_size = self.capacity * size_of::<u64>();
893910
let keys_size = self.capacity * size_of::<K>();
894911
let vals_size = self.capacity * size_of::<V>();
895-
let (align, _, _, _, size) = calculate_offsets(hashes_size, min_align_of::<u64>(),
896-
keys_size, min_align_of::<K>(),
897-
vals_size, min_align_of::<V>());
912+
let (align, _, size) = calculate_allocation(hashes_size, min_align_of::<u64>(),
913+
keys_size, min_align_of::<K>(),
914+
vals_size, min_align_of::<V>());
898915

899916
unsafe {
900917
deallocate(self.hashes as *mut u8, size, align);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// error-pattern:capacity overflow
12+
13+
use std::collections::hashmap::HashMap;
14+
use std::uint;
15+
use std::mem::size_of;
16+
17+
fn main() {
18+
let threshold = uint::MAX / size_of::<(u64, u64, u64)>();
19+
let mut h = HashMap::<u64, u64>::with_capacity(threshold + 100);
20+
h.insert(0, 0);
21+
}

0 commit comments

Comments
 (0)