diff --git a/src/c_vec.rs b/src/c_vec.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e9036fe5a2578792e0088d5d9b1805f79be91395
--- /dev/null
+++ b/src/c_vec.rs
@@ -0,0 +1,175 @@
+use core::iter::IntoIterator;
+use core::ops::{Deref, DerefMut};
+use core::ptr::{self, NonNull};
+use core::{cmp, mem, slice};
+use crate::platform::types::*;
+use crate::platform;
+
+/// Error that occurs when an allocation fails
+#[derive(Debug, Default, Hash, PartialEq, Eq, Clone, Copy)]
+pub struct AllocError;
+
+/// A normal vector allocated in Rust needs to be dropped from Rust
+/// too, in order to avoid UB. This CVec is an abstraction that works
+/// using only C allocations functions and can therefore be dropped
+/// from C. Just like the Rust Vec, this does bounds checks to assure
+/// you never reach isize::MAX. Unless you need to drop something from
+/// C, prefer Rust's builtin Vec.
+pub struct CVec<T> {
+    ptr: NonNull<T>,
+    len: usize,
+    cap: usize
+}
+impl<T> CVec<T> {
+    pub fn new() -> Self {
+        Self {
+            ptr: NonNull::dangling(),
+            len: 0,
+            cap: 0
+        }
+    }
+    fn check_bounds(i: usize) -> Result<usize, AllocError> {
+        if i > core::isize::MAX as usize {
+            Err(AllocError)
+        } else {
+            Ok(i)
+        }
+    }
+    fn check_mul(x: usize, y: usize) -> Result<usize, AllocError> {
+        x.checked_mul(y).ok_or(AllocError)
+            .and_then(Self::check_bounds)
+    }
+    pub fn with_capacity(cap: usize) -> Result<Self, AllocError> {
+        if cap == 0 {
+            return Ok(Self::new());
+        }
+        let size = Self::check_mul(cap, mem::size_of::<T>())?;
+        let ptr = NonNull::new(unsafe { platform::alloc(size) as *mut T }).ok_or(AllocError)?;
+        Ok(Self {
+            ptr,
+            len: 0,
+            cap
+        })
+    }
+    unsafe fn resize(&mut self, cap: usize) -> Result<(), AllocError> {
+        let size = Self::check_mul(cap, mem::size_of::<T>())?;
+        let ptr = NonNull::new(platform::realloc(self.ptr.as_ptr() as *mut c_void, size) as *mut T).ok_or(AllocError)?;
+        self.ptr = ptr;
+        self.cap = cap;
+        Ok(())
+    }
+    unsafe fn drop_range(&mut self, start: usize, end: usize) {
+        let mut start = self.ptr.as_ptr().add(start);
+        let end = self.ptr.as_ptr().add(end);
+        while start < end {
+            ptr::drop_in_place(start);
+            start = start.add(1);
+        }
+    }
+    pub fn reserve(&mut self, required: usize) -> Result<(), AllocError> {
+        let reserved_len = self.len.checked_add(required)
+            .ok_or(AllocError)
+            .and_then(Self::check_bounds)?;
+        let new_cap = cmp::min(reserved_len.next_power_of_two(), core::isize::MAX as usize);
+        if new_cap > self.cap {
+            unsafe {
+                self.resize(new_cap)?;
+            }
+        }
+        Ok(())
+    }
+    pub fn push(&mut self, elem: T) -> Result<(), AllocError> {
+        unsafe {
+            self.reserve(1)?;
+            ptr::write(self.ptr.as_ptr().add(self.len), elem);
+        }
+        self.len += 1; // no need to bounds check, as new len <= cap
+        Ok(())
+    }
+    pub fn extend_from_slice(&mut self, elems: &[T]) -> Result<(), AllocError>
+        where T: Copy
+    {
+        unsafe {
+            self.reserve(elems.len())?;
+            ptr::copy_nonoverlapping(elems.as_ptr(), self.ptr.as_ptr().add(self.len), elems.len());
+        }
+        self.len += elems.len(); // no need to bounds check, as new len <= cap
+        Ok(())
+    }
+    pub fn append(&mut self, other: &mut Self) -> Result<(), AllocError> {
+        unsafe {
+            self.reserve(other.len())?;
+            ptr::copy_nonoverlapping(other.as_ptr(), self.ptr.as_ptr().add(self.len), other.len());
+        }
+        self.len += other.len(); // no need to bounds check, as new len <= cap
+        Ok(())
+    }
+    pub fn truncate(&mut self, len: usize) {
+        if len < self.len {
+            unsafe {
+                self.drop_range(len, self.len);
+            }
+            self.len = len;
+        }
+    }
+    pub fn shrink_to_fit(&mut self) -> Result<(), AllocError> {
+        if self.len < self.cap {
+            unsafe {
+                self.resize(self.len)?;
+            }
+        }
+        Ok(())
+    }
+    pub fn capacity(&self) -> usize {
+        self.cap
+    }
+    pub fn as_ptr(&self) -> *const T {
+        self.ptr.as_ptr()
+    }
+    pub fn as_mut_ptr(&mut self) -> *mut T {
+        self.ptr.as_ptr()
+    }
+    /// Leaks the inner data. This is safe to drop from C!
+    pub fn leak(mut self) -> *mut T {
+        let ptr = self.as_mut_ptr();
+        mem::forget(self);
+        ptr
+    }
+}
+impl<T> Deref for CVec<T> {
+    type Target = [T];
+
+    fn deref(&self) -> &Self::Target {
+        unsafe {
+            slice::from_raw_parts(self.ptr.as_ptr(), self.len)
+        }
+    }
+}
+impl<T> DerefMut for CVec<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        unsafe {
+            slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len)
+        }
+    }
+}
+impl<T> Drop for CVec<T> {
+    fn drop(&mut self) {
+        unsafe {
+            self.drop_range(0, self.len);
+        }
+    }
+}
+impl<'a, T> IntoIterator for &'a CVec<T> {
+    type Item = <&'a [T] as IntoIterator>::Item;
+    type IntoIter = <&'a [T] as IntoIterator>::IntoIter;
+    fn into_iter(self) -> Self::IntoIter {
+        <&[T]>::into_iter(&*self)
+    }
+}
+impl<'a, T> IntoIterator for &'a mut CVec<T> {
+    type Item = <&'a mut [T] as IntoIterator>::Item;
+    type IntoIter = <&'a mut [T] as IntoIterator>::IntoIter;
+    fn into_iter(self) -> Self::IntoIter {
+        <&mut [T]>::into_iter(&mut *self)
+    }
+}
diff --git a/src/header/dirent/mod.rs b/src/header/dirent/mod.rs
index 8696b9ad01669fed2e3a1c39ad3b66f610747d4e..e0f9eb26b091381742517bdcf8c6b2012379e712 100644
--- a/src/header/dirent/mod.rs
+++ b/src/header/dirent/mod.rs
@@ -4,12 +4,13 @@ use alloc::boxed::Box;
 use core::{mem, ptr};
 
 use c_str::CStr;
+use c_vec::CVec;
 use fs::File;
 use header::{errno, fcntl, stdlib, string};
 use io::{Seek, SeekFrom};
-use platform;
 use platform::types::*;
 use platform::{Pal, Sys};
+use platform;
 
 const DIR_BUF_SIZE: usize = mem::size_of::<dirent>() * 3;
 
@@ -134,15 +135,16 @@ pub unsafe extern "C" fn scandir(
         return -1;
     }
 
-    let old_errno = platform::errno;
+    let mut vec = match CVec::with_capacity(4) {
+        Ok(vec) => vec,
+        Err(err) => return -1
+    };
 
-    let mut len: isize = 0;
-    let mut cap: isize = 4;
-    *namelist = platform::alloc(cap as usize * mem::size_of::<*mut dirent>()) as *mut *mut dirent;
+    let old_errno = platform::errno;
+    platform::errno = 0;
 
     loop {
-        platform::errno = 0;
-        let entry = readdir(dir);
+        let entry: *mut dirent = readdir(dir);
         if entry.is_null() {
             break;
         }
@@ -153,30 +155,31 @@ pub unsafe extern "C" fn scandir(
             }
         }
 
-        if len >= cap {
-            cap *= 2;
-            *namelist = platform::realloc(
-                *namelist as *mut c_void,
-                cap as usize * mem::size_of::<*mut dirent>(),
-            ) as *mut *mut dirent;
-        }
-
         let copy = platform::alloc(mem::size_of::<dirent>()) as *mut dirent;
-        *copy = (*entry).clone();
-        *(*namelist).offset(len) = copy;
-        len += 1;
+        if copy.is_null() {
+            break;
+        }
+        ptr::write(copy, (*entry).clone());
+        if let Err(_) = vec.push(copy) {
+            break;
+        }
     }
 
     closedir(dir);
 
+    let len = vec.len();
+    if let Err(_) = vec.shrink_to_fit() {
+        return -1;
+    }
+
     if platform::errno != 0 {
-        while len > 0 {
-            len -= 1;
-            platform::free(*(*namelist).offset(len) as *mut c_void);
+        for ptr in &mut vec {
+            platform::free(*ptr as *mut c_void);
         }
-        platform::free(*namelist as *mut c_void);
         -1
     } else {
+        *namelist = vec.leak();
+
         platform::errno = old_errno;
         stdlib::qsort(
             *namelist as *mut c_void,
@@ -184,6 +187,7 @@ pub unsafe extern "C" fn scandir(
             mem::size_of::<*mut dirent>(),
             mem::transmute(compare),
         );
+
         len as c_int
     }
 }
diff --git a/src/header/stdio/scanf.rs b/src/header/stdio/scanf.rs
index 6eb85b3481077375115a48ed1c2233441cc7bbd0..1d3d5e2106782547bac05f3b8001835ae75eee14 100644
--- a/src/header/stdio/scanf.rs
+++ b/src/header/stdio/scanf.rs
@@ -404,8 +404,7 @@ unsafe fn inner_scanf<R: Read>(
                     let mut ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) };
 
                     // While we haven't used up all the width, and it matches
-                    while width.map(|w| w > 0).unwrap_or(true) && !invert == matches.contains(&byte)
-                    {
+                    while width.map(|w| w > 0).unwrap_or(true) && !invert == matches.contains(&byte) {
                         if let Some(ref mut ptr) = ptr {
                             **ptr = byte as c_char;
                             *ptr = ptr.offset(1);
diff --git a/src/lib.rs b/src/lib.rs
index 2489dc879b568ba647b4813a6dc93c98ce2a6e2c..3ea4a1f0613d04d5567e28ee4d4b6073e3c5f839 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -51,6 +51,7 @@ extern crate spin;
 #[macro_use]
 mod macros;
 pub mod c_str;
+pub mod c_vec;
 pub mod cxa;
 pub mod db;
 pub mod fs;
diff --git a/tests/stdio/scanf.c b/tests/stdio/scanf.c
index c9ed3f438abfd1204e64ce90eab24d1648cb663a..f2fc626793b5d39ef884145daebafea4921bdd2d 100644
--- a/tests/stdio/scanf.c
+++ b/tests/stdio/scanf.c
@@ -54,10 +54,28 @@ int main(void) {
     char hostbuf[100];
     char pathbuf[100];
 
+    // don't push NUL, make sure scanf does that
+    memset(protobuf, 97, 16);
+    memset(slashbuf, 97, 4);
+    memset(hostbuf, 97, 100);
+    memset(pathbuf, 97, 100);
+
     int ret = sscanf(
         "https://redox-os.org", "%15[^\n/:]:%3[/]%[^\n/?#]%[^\n]",
         &protobuf, &slashbuf, &hostbuf, &pathbuf
     );
+    if (ret < 4) {
+        *pathbuf = 0;
+    }
+    if (ret < 3) {
+        *hostbuf = 0;
+    }
+    if (ret < 2) {
+        *slashbuf = 0;
+    }
+    if (ret < 1) {
+        *protobuf = 0;
+    }
 
     printf("%d \"%s\" \"%s\" \"%s\" \"%s\"\n", ret, &protobuf, &slashbuf, &hostbuf, &pathbuf);
 }