From 568c97c77f09d75e91e6f170a05ffe29fcfcdfb9 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Thu, 26 Oct 2017 17:12:07 +0200 Subject: [PATCH] Fix SEEK_END, file offset overflow, and read/write/mkpartition syscall bugs. Fix SEEK_END seeking twice as far as requested. Centralize lseek handling in one place and avoid overflow bugs. Inode lseek handlers now only need to handle SEEK_END with offset 0. Prevent the file offset from ever going below zero or overflowing. Character devices are now not seekable, but lseek will pretend they are, yet always stay at the file offset 0. pread/pwrite on character devices will now ignore the file offset and call read/write. This change prevents character devices from being memory mapped, notably /dev/zero can no longer be memory mapped. None of the current ports seem to rely on this behavior and will work with just MAP_ANONYMOUS. Refactor read and write system calls to have a shared return statement for both seekable and non-seekable IO. Fix file offset overflow bugs in read and write system calls. Fix system calls returning EPERM instead of properly returning EBADF when the file has not been opened in the right mode. Truncate IO counts and total vector IO length so the IO operation does not do any IO beyond OFF_MAX. Truncate also total vector IO length for recvmsg and sendmsg. Fail with EINVAL if total vector IO length exceeds SSIZE_MAX. Don't stop early if the total IO length is zero, so zero length IO now block on any locks internal to the inode. Handle reads at the maximum file offset with an end of file condition and handle writes of at least one byte at the maximum file offset by failing with EFBIG. Refactor UtilMemoryBuffer to store the file size using off_t instead of size_t to avoid casts and keep file sizes in the off_t type. Properly handle errors in the code, such as failing with EROFS instead of EBADF if the backing memory is not writeable, and failing with EFBIG if writing beyond the end of the file. Fix mkpartition not rejecting invalid partition start offsets and lengths. Strictly enforce partition start and length checks in the partition code. Enforce partitions exist within regular files or block devices. Fix a few indention issues. --- kernel/descriptor.cpp | 255 +++++++++++++++------- kernel/disk/node.cpp | 13 +- kernel/fcache.cpp | 6 +- kernel/fs/full.cpp | 10 - kernel/fs/full.h | 2 - kernel/fs/null.cpp | 12 +- kernel/fs/null.h | 2 - kernel/fs/random.cpp | 21 +- kernel/fs/random.h | 2 - kernel/fs/user.cpp | 2 + kernel/fs/util.cpp | 46 ++-- kernel/fs/util.h | 4 +- kernel/fs/zero.cpp | 15 +- kernel/fs/zero.h | 2 - kernel/include/sortix/kernel/descriptor.h | 1 + kernel/inode.cpp | 4 +- kernel/io.cpp | 8 + kernel/memorymanagement.cpp | 2 +- kernel/partition.cpp | 36 ++- kernel/pipe.cpp | 4 + 20 files changed, 261 insertions(+), 186 deletions(-) diff --git a/kernel/descriptor.cpp b/kernel/descriptor.cpp index 2a7055a0..cb7a3f64 100644 --- a/kernel/descriptor.cpp +++ b/kernel/descriptor.cpp @@ -119,6 +119,32 @@ Ref OpenDirContainingPath(ioctx_t* ctx, return ret; } +size_t TruncateIOVec(struct iovec* iov, int iovcnt, off_t limit) +{ + assert(0 <= iovcnt); + assert(0 <= limit); + off_t left = limit; + if ( (uintmax_t) SSIZE_MAX <= (uintmax_t) left ) + left = SSIZE_MAX; + size_t requested = 0; + for ( int i = 0; i < iovcnt; i++ ) + { + size_t request = iov[i].iov_len; + if ( __builtin_add_overflow(requested, request, &requested) ) + requested = SIZE_MAX; + if ( left == 0 ) + iov[i].iov_len = 0; + else if ( (uintmax_t) left < (uintmax_t) request ) + { + iov[i].iov_len = left; + left = 0; + } + else + left -= request; + } + return requested; +} + // TODO: Add security checks. Descriptor::Descriptor() @@ -192,13 +218,14 @@ Ref Descriptor::Fork() bool Descriptor::IsSeekable() { + if ( S_ISCHR(type) ) + return false; ScopedLock lock(¤t_offset_lock); if ( !checked_seekable ) { - // TODO: Is this enough? Check that errno happens to be ESPIPE? int saved_errno = errno; ioctx_t ctx; SetupKernelIOCtx(&ctx); - seekable = S_ISDIR(vnode->type) || 0 <= vnode->lseek(&ctx, SEEK_SET, 0); + seekable = S_ISDIR(vnode->type) || 0 <= vnode->lseek(&ctx, 0, SEEK_END); checked_seekable = true; errno = saved_errno; } @@ -242,18 +269,26 @@ int Descriptor::truncate(ioctx_t* ctx, off_t length) if ( length < 0 ) return errno = EINVAL, -1; if ( !(dflags & O_WRITE) ) - return errno = EPERM, -1; + return errno = EBADF, -1; return vnode->truncate(ctx, length); } off_t Descriptor::lseek(ioctx_t* ctx, off_t offset, int whence) { - // TODO: Possible information leak to let someone without O_READ | O_WRITE - // seek the file and get information about data holes. + if ( S_ISCHR(type) ) + { + if ( whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END ) + return errno = EINVAL, -1; + return 0; + } + if ( !IsSeekable() ) - return vnode->lseek(ctx, offset, whence); + return errno = ESPIPE, -1; ScopedLock lock(¤t_offset_lock); + + // TODO: Possible information leak to let someone without O_READ | O_WRITE + // seek the file and get information about data holes. off_t reloff; if ( whence == SEEK_SET ) reloff = 0; @@ -261,48 +296,51 @@ off_t Descriptor::lseek(ioctx_t* ctx, off_t offset, int whence) reloff = current_offset; else if ( whence == SEEK_END ) { - if ( (reloff = vnode->lseek(ctx, offset, SEEK_END)) < 0 ) + if ( (reloff = vnode->lseek(ctx, 0, SEEK_END)) < 0 ) return -1; } else return errno = EINVAL, -1; - if ( offset < 0 && reloff + offset < 0 ) - return errno = EOVERFLOW, -1; - if ( OFF_MAX - current_offset < offset ) + off_t new_offset; + if ( __builtin_add_overflow(reloff, offset, &new_offset) ) return errno = EOVERFLOW, -1; + if ( new_offset < 0 ) + return errno = EINVAL, -1; - return current_offset = reloff + offset; + return current_offset = new_offset; } ssize_t Descriptor::read(ioctx_t* ctx, uint8_t* buf, size_t count) { if ( !(dflags & O_READ) ) - return errno = EPERM, -1; - if ( !count ) - return 0; + return errno = EBADF, -1; if ( SIZE_MAX < count ) count = SSIZE_MAX; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); + ssize_t result; if ( !IsSeekable() ) + result = vnode->read(ctx, buf, count); + else { - ssize_t result = vnode->read(ctx, buf, count); - ctx->dflags = old_ctx_dflags; - return result; + ScopedLock lock(¤t_offset_lock); + off_t available = OFF_MAX - current_offset; + if ( (uintmax_t) available < (uintmax_t) count ) + count = available; + result = vnode->pread(ctx, buf, count, current_offset); + if ( 0 < result && + __builtin_add_overflow(current_offset, result, ¤t_offset) ) + current_offset = OFF_MAX; } - ScopedLock lock(¤t_offset_lock); - ssize_t ret = vnode->pread(ctx, buf, count, current_offset); - if ( 0 <= ret ) - current_offset += ret; ctx->dflags = old_ctx_dflags; - return ret; + return result; } ssize_t Descriptor::readv(ioctx_t* ctx, const struct iovec* iov_ptr, int iovcnt) { if ( !(dflags & O_READ) ) - return errno = EPERM, -1; + return errno = EBADF, -1; if ( iovcnt < 0 || IOV_MAX < iovcnt ) return errno = EINVAL, -1; struct iovec* iov = new struct iovec[iovcnt]; @@ -313,32 +351,46 @@ ssize_t Descriptor::readv(ioctx_t* ctx, const struct iovec* iov_ptr, int iovcnt) return delete[] iov, -1; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); + ssize_t result = -1; if ( !IsSeekable() ) { - ssize_t result = vnode->readv(ctx, iov, iovcnt); - ctx->dflags = old_ctx_dflags; - delete[] iov; - return result; + if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, SSIZE_MAX) ) + errno = EINVAL; + else + result = vnode->readv(ctx, iov, iovcnt); + } + else + { + ScopedLock lock(¤t_offset_lock); + off_t available = OFF_MAX - current_offset; + if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, available) ) + errno = EINVAL; + else + result = vnode->preadv(ctx, iov, iovcnt, current_offset); + if ( 0 < result && + __builtin_add_overflow(current_offset, result, ¤t_offset) ) + current_offset = OFF_MAX; } - ScopedLock lock(¤t_offset_lock); - ssize_t ret = vnode->preadv(ctx, iov, iovcnt, current_offset); - if ( 0 <= ret ) - current_offset += ret; ctx->dflags = old_ctx_dflags; delete[] iov; - return ret; + return result; } ssize_t Descriptor::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off) { + if ( S_ISCHR(type) ) + return read(ctx, buf, count); if ( !(dflags & O_READ) ) - return errno = EPERM, -1; + return errno = EBADF, -1; + if ( !IsSeekable() ) + return errno = ESPIPE, -1; if ( off < 0 ) return errno = EINVAL, -1; - if ( !count ) - return 0; if ( SSIZE_MAX < count ) count = SSIZE_MAX; + off_t available = OFF_MAX - off; + if ( (uintmax_t) available < (uintmax_t) count ) + count = available; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); ssize_t result = vnode->pread(ctx, buf, count, off); @@ -349,8 +401,12 @@ ssize_t Descriptor::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off) ssize_t Descriptor::preadv(ioctx_t* ctx, const struct iovec* iov_ptr, int iovcnt, off_t off) { + if ( S_ISCHR(type) ) + return readv(ctx, iov_ptr, iovcnt); if ( !(dflags & O_READ) ) - return errno = EPERM, -1; + return errno = EBADF, -1; + if ( !IsSeekable() ) + return errno = ESPIPE, -1; if ( off < 0 || iovcnt < 0 || IOV_MAX < iovcnt ) return errno = EINVAL, -1; struct iovec* iov = new struct iovec[iovcnt]; @@ -359,6 +415,9 @@ ssize_t Descriptor::preadv(ioctx_t* ctx, const struct iovec* iov_ptr, size_t iov_size = sizeof(struct iovec) * iovcnt; if ( !ctx->copy_from_src(iov, iov_ptr, iov_size) ) return delete[] iov, -1; + off_t available = OFF_MAX - off; + if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, available) ) + return delete[] iov, errno = EINVAL, -1; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); ssize_t result = vnode->preadv(ctx, iov, iovcnt, off); @@ -370,42 +429,49 @@ ssize_t Descriptor::preadv(ioctx_t* ctx, const struct iovec* iov_ptr, ssize_t Descriptor::write(ioctx_t* ctx, const uint8_t* buf, size_t count) { if ( !(dflags & O_WRITE) ) - return errno = EPERM, -1; - if ( !count ) - return 0; + return errno = EBADF, -1; if ( SSIZE_MAX < count ) count = SSIZE_MAX; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); + ssize_t result; if ( !IsSeekable() ) + result = vnode->write(ctx, buf, count); + else { - ssize_t result = vnode->write(ctx, buf, count); - ctx->dflags = old_ctx_dflags; - return result; - } - ScopedLock lock(¤t_offset_lock); - if ( ctx->dflags & O_APPEND ) - { - off_t end = vnode->lseek(ctx, 0, SEEK_END); - if ( end < 0 ) + ScopedLock lock(¤t_offset_lock); + if ( ctx->dflags & O_APPEND ) { - ctx->dflags = old_ctx_dflags; - return -1; + off_t end = vnode->lseek(ctx, 0, SEEK_END); + if ( 0 <= end ) + current_offset = end; + } + if ( current_offset == OFF_MAX && count ) + { + errno = EFBIG; + result = -1; + } + else + { + off_t available = OFF_MAX - current_offset; + if ( (uintmax_t) available < (uintmax_t) count ) + count = available; + result = vnode->pwrite(ctx, buf, count, current_offset); + if ( 0 < result && + __builtin_add_overflow(current_offset, result, + ¤t_offset) ) + current_offset = OFF_MAX; } - current_offset = end; } - ssize_t ret = vnode->pwrite(ctx, buf, count, current_offset); - if ( 0 <= ret ) - current_offset += ret; ctx->dflags = old_ctx_dflags; - return ret; + return result; } ssize_t Descriptor::writev(ioctx_t* ctx, const struct iovec* iov_ptr, int iovcnt) { if ( !(dflags & O_WRITE) ) - return errno = EPERM, -1; + return errno = EBADF, -1; if ( iovcnt < 0 || IOV_MAX < iovcnt ) return errno = EINVAL, -1; struct iovec* iov = new struct iovec[iovcnt]; @@ -416,43 +482,61 @@ ssize_t Descriptor::writev(ioctx_t* ctx, const struct iovec* iov_ptr, return delete[] iov, -1; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); + ssize_t result = -1; if ( !IsSeekable() ) { - ssize_t result = vnode->writev(ctx, iov, iovcnt); - ctx->dflags = old_ctx_dflags; - delete[] iov; - return result; + if ( SSIZE_MAX < TruncateIOVec(iov, iovcnt, SSIZE_MAX) ) + errno = EINVAL; + else + result = vnode->writev(ctx, iov, iovcnt); } - ScopedLock lock(¤t_offset_lock); - if ( ctx->dflags & O_APPEND ) + else { - off_t end = vnode->lseek(ctx, 0, SEEK_END); - if ( end < 0 ) + ScopedLock lock(¤t_offset_lock); + if ( ctx->dflags & O_APPEND ) { - ctx->dflags = old_ctx_dflags; - return -1; + off_t end = vnode->lseek(ctx, 0, SEEK_END); + if ( 0 <= end ) + current_offset = end; + } + off_t available = OFF_MAX - current_offset; + size_t count = TruncateIOVec(iov, iovcnt, available); + if ( SSIZE_MAX < count ) + errno = EINVAL; + else if ( current_offset == OFF_MAX && count ) + errno = EFBIG; + else + { + result = vnode->pwritev(ctx, iov, iovcnt, current_offset); + if ( 0 < result && + __builtin_add_overflow(current_offset, result, + ¤t_offset) ) + current_offset = OFF_MAX; } - current_offset = end; } - ssize_t ret = vnode->pwritev(ctx, iov, iovcnt, current_offset); - if ( 0 <= ret ) - current_offset += ret; ctx->dflags = old_ctx_dflags; delete[] iov; - return ret; + return result; } ssize_t Descriptor::pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count, off_t off) { + if ( S_ISCHR(type) ) + return write(ctx, buf, count); if ( !(dflags & O_WRITE) ) - return errno = EPERM, -1; + return errno = EBADF, -1; + if ( !IsSeekable() ) + return errno = ESPIPE, -1; if ( off < 0 ) return errno = EINVAL, -1; - if ( !count ) - return 0; + if ( off == OFF_MAX && count ) + return errno = EFBIG, -1; if ( SSIZE_MAX < count ) count = SSIZE_MAX; + off_t available = OFF_MAX - off; + if ( (uintmax_t) available < (uintmax_t) count ) + count = available; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); ssize_t result = vnode->pwrite(ctx, buf, count, off); @@ -463,8 +547,12 @@ ssize_t Descriptor::pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count, ssize_t Descriptor::pwritev(ioctx_t* ctx, const struct iovec* iov_ptr, int iovcnt, off_t off) { + if ( S_ISCHR(type) ) + return writev(ctx, iov_ptr, iovcnt); if ( !(dflags & O_WRITE) ) - return errno = EPERM, -1; + return errno = EBADF, -1; + if ( !IsSeekable() ) + return errno = ESPIPE, -1; if ( off < 0 || iovcnt < 0 || IOV_MAX < iovcnt ) return errno = EINVAL, -1; struct iovec* iov = new struct iovec[iovcnt]; @@ -473,6 +561,12 @@ ssize_t Descriptor::pwritev(ioctx_t* ctx, const struct iovec* iov_ptr, size_t iov_size = sizeof(struct iovec) * iovcnt; if ( !ctx->copy_from_src(iov, iov_ptr, iov_size) ) return delete[] iov, -1; + off_t available = OFF_MAX - off; + size_t count = TruncateIOVec(iov, iovcnt, available); + if ( SSIZE_MAX < count ) + return delete[] iov, errno = EINVAL, -1; + if ( off == OFF_MAX && count != 0 ) + return delete[] iov, errno = EFBIG, -1; int old_ctx_dflags = ctx->dflags; ctx->dflags = ContextFlags(old_ctx_dflags, dflags); ssize_t result = vnode->pwritev(ctx, iov, iovcnt, off); @@ -529,15 +623,18 @@ ssize_t Descriptor::readdirents(ioctx_t* ctx, // it with the O_EXEC flag - POSIX allows that and it makes sense // because the execute bit on directories control search permission. if ( !(dflags & (O_SEARCH | O_READ | O_WRITE)) ) - return errno = EPERM, -1; + return errno = EBADF, -1; if ( SSIZE_MAX < size ) size = SSIZE_MAX; if ( size < sizeof(*dirent) ) return errno = EINVAL, -1; ScopedLock lock(¤t_offset_lock); + if ( current_offset == OFF_MAX && size ) + return 0; ssize_t ret = vnode->readdirents(ctx, dirent, size, current_offset); - if ( 0 < ret ) - current_offset++; + if ( 0 < ret && + __builtin_add_overflow(current_offset, 1, ¤t_offset) ) + current_offset = OFF_MAX; return ret; } @@ -803,7 +900,7 @@ int Descriptor::rename_here(ioctx_t* ctx, Ref from, ssize_t Descriptor::readlink(ioctx_t* ctx, char* buf, size_t bufsize) { if ( !(dflags & O_READ) ) - return errno = EPERM, -1; + return errno = EBADF, -1; if ( SSIZE_MAX < bufsize ) bufsize = SSIZE_MAX; return vnode->readlink(ctx, buf, bufsize); diff --git a/kernel/disk/node.cpp b/kernel/disk/node.cpp index cc75636b..884e9312 100644 --- a/kernel/disk/node.cpp +++ b/kernel/disk/node.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * Copyright (c) 2011-2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -71,15 +71,8 @@ int PortNode::truncate(ioctx_t* /*ctx*/, off_t length) off_t PortNode::lseek(ioctx_t* /*ctx*/, off_t offset, int whence) { - if ( whence == SEEK_SET ) - return offset; - if ( whence == SEEK_END ) - { - off_t result; - if ( __builtin_add_overflow(harddisk->GetSize(), offset, &result) ) - return errno = EOVERFLOW, -1; - return result; - } + if ( whence == SEEK_END && offset == 0 ) + return (off_t) harddisk->GetSize(); return errno = EINVAL, -1; } diff --git a/kernel/fcache.cpp b/kernel/fcache.cpp index 4513374d..7c5b3c51 100644 --- a/kernel/fcache.cpp +++ b/kernel/fcache.cpp @@ -439,10 +439,8 @@ off_t FileCache::GetFileSize() off_t FileCache::lseek(ioctx_t* /*ctx*/, off_t offset, int whence) { ScopedLock lock(&fcache_mutex); - if ( whence == SEEK_SET ) - return offset; - if ( whence == SEEK_END ) - return (off_t) file_size + offset; + if ( whence == SEEK_END && offset == 0 ) + return (off_t) file_size; return errno = EINVAL, -1; } diff --git a/kernel/fs/full.cpp b/kernel/fs/full.cpp index 8913f913..64a5a63d 100644 --- a/kernel/fs/full.cpp +++ b/kernel/fs/full.cpp @@ -53,16 +53,6 @@ Full::~Full() { } -int Full::truncate(ioctx_t* /*ctx*/, off_t /*length*/) -{ - return 0; -} - -off_t Full::lseek(ioctx_t* /*ctx*/, off_t offset, int /*whence*/) -{ - return offset; -} - ssize_t Full::read(ioctx_t* ctx, uint8_t* buf, size_t count) { ctx->zero_dest(buf, count); diff --git a/kernel/fs/full.h b/kernel/fs/full.h index 1b0a0661..08986c75 100644 --- a/kernel/fs/full.h +++ b/kernel/fs/full.h @@ -29,8 +29,6 @@ class Full : public AbstractInode public: Full(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode); virtual ~Full(); - virtual int truncate(ioctx_t* ctx, off_t length); - virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence); virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off); virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); diff --git a/kernel/fs/null.cpp b/kernel/fs/null.cpp index d75fefa5..51ef997c 100644 --- a/kernel/fs/null.cpp +++ b/kernel/fs/null.cpp @@ -50,16 +50,6 @@ Null::~Null() { } -int Null::truncate(ioctx_t* /*ctx*/, off_t /*length*/) -{ - return 0; -} - -off_t Null::lseek(ioctx_t* /*ctx*/, off_t offset, int /*whence*/) -{ - return offset; -} - ssize_t Null::read(ioctx_t* /*ctx*/, uint8_t* /*buf*/, size_t /*count*/) { return 0; @@ -77,7 +67,7 @@ ssize_t Null::write(ioctx_t* /*ctx*/, const uint8_t* /*buf*/, size_t count) } ssize_t Null::pwrite(ioctx_t* /*ctx*/, const uint8_t* /*buf*/, size_t count, - off_t /*off*/) + off_t /*off*/) { return count; } diff --git a/kernel/fs/null.h b/kernel/fs/null.h index c05df25d..ccce41df 100644 --- a/kernel/fs/null.h +++ b/kernel/fs/null.h @@ -29,8 +29,6 @@ class Null : public AbstractInode public: Null(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode); virtual ~Null(); - virtual int truncate(ioctx_t* ctx, off_t length); - virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence); virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off); virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); diff --git a/kernel/fs/random.cpp b/kernel/fs/random.cpp index 46d6043c..b861b7cd 100644 --- a/kernel/fs/random.cpp +++ b/kernel/fs/random.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include @@ -32,7 +31,8 @@ namespace Sortix { -DevRandom::DevRandom(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode) +DevRandom::DevRandom(dev_t dev, ino_t ino, uid_t owner, gid_t group, + mode_t mode) { inode_type = INODE_TYPE_STREAM; if ( !dev ) @@ -53,16 +53,6 @@ DevRandom::~DevRandom() { } -int DevRandom::truncate(ioctx_t* /*ctx*/, off_t /*length*/) -{ - return 0; -} - -off_t DevRandom::lseek(ioctx_t* /*ctx*/, off_t offset, int /*whence*/) -{ - return offset; -} - ssize_t DevRandom::read(ioctx_t* ctx, uint8_t* buf, size_t count) { size_t sofar = 0; @@ -82,7 +72,8 @@ ssize_t DevRandom::read(ioctx_t* ctx, uint8_t* buf, size_t count) return (ssize_t) sofar; } -ssize_t DevRandom::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t /*off*/) +ssize_t DevRandom::pread(ioctx_t* ctx, uint8_t* buf, size_t count, + off_t /*off*/) { return read(ctx, buf, count); } @@ -92,8 +83,8 @@ ssize_t DevRandom::write(ioctx_t* /*ctx*/, const uint8_t* /*buf*/, size_t count) return count; } -ssize_t DevRandom::pwrite(ioctx_t* /*ctx*/, const uint8_t* /*buf*/, size_t count, - off_t /*off*/) +ssize_t DevRandom::pwrite(ioctx_t* /*ctx*/, const uint8_t* /*buf*/, + size_t count, off_t /*off*/) { return count; } diff --git a/kernel/fs/random.h b/kernel/fs/random.h index 3f6a2644..db1659ef 100644 --- a/kernel/fs/random.h +++ b/kernel/fs/random.h @@ -29,8 +29,6 @@ class DevRandom : public AbstractInode public: DevRandom(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode); virtual ~DevRandom(); - virtual int truncate(ioctx_t* ctx, off_t length); - virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence); virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off); virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); diff --git a/kernel/fs/user.cpp b/kernel/fs/user.cpp index c6d3af16..de4735ec 100644 --- a/kernel/fs/user.cpp +++ b/kernel/fs/user.cpp @@ -876,6 +876,8 @@ int Unode::truncate(ioctx_t* ctx, off_t length) off_t Unode::lseek(ioctx_t* ctx, off_t offset, int whence) { + if ( whence != SEEK_END && offset != 0 ) + return errno = EINVAL, -1; Channel* channel = server->Connect(ctx); if ( !channel ) return -1; diff --git a/kernel/fs/util.cpp b/kernel/fs/util.cpp index 3fbeabbc..1cb19a80 100644 --- a/kernel/fs/util.cpp +++ b/kernel/fs/util.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Jonas 'Sortie' Termansen. + * Copyright (c) 2012, 2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,7 @@ * Utility classes for kernel filesystems. */ +#include #include #include @@ -36,8 +37,9 @@ namespace Sortix { UtilMemoryBuffer::UtilMemoryBuffer(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode, uint8_t* buf, - size_t bufsize, bool write, bool deletebuf) + off_t bufsize, bool write, bool deletebuf) { + assert(0 <= bufsize); inode_type = INODE_TYPE_FILE; this->filelock = KTHREAD_MUTEX_INITIALIZER; this->stat_uid = owner; @@ -45,7 +47,7 @@ UtilMemoryBuffer::UtilMemoryBuffer(dev_t dev, ino_t ino, uid_t owner, this->type = S_IFREG; this->stat_mode = (mode & S_SETABLE) | this->type; this->stat_blksize = 1; - this->stat_size = (off_t) bufsize; + this->stat_size = bufsize; this->dev = dev; this->ino = ino ? ino : (ino_t) this; this->buf = buf; @@ -63,30 +65,27 @@ UtilMemoryBuffer::~UtilMemoryBuffer() int UtilMemoryBuffer::truncate(ioctx_t* /*ctx*/, off_t length) { ScopedLock lock(&filelock); - if ( (uintmax_t) length != (uintmax_t) bufsize ) - return errno = ENOTSUP, -1; + if ( length != bufsize ) + return errno = EPERM, -1; return 0; } off_t UtilMemoryBuffer::lseek(ioctx_t* /*ctx*/, off_t offset, int whence) { ScopedLock lock(&filelock); - if ( whence == SEEK_SET ) - return offset; - if ( whence == SEEK_END ) - return (off_t) bufsize + offset; - errno = EINVAL; - return -1; + if ( whence == SEEK_END && offset == 0 ) + return bufsize; + return errno = EINVAL, -1; } ssize_t UtilMemoryBuffer::pread(ioctx_t* ctx, uint8_t* dest, size_t count, off_t off) { ScopedLock lock(&filelock); - if ( (uintmax_t) bufsize < (uintmax_t) off ) + if ( bufsize < off ) return 0; - size_t available = bufsize - off; - if ( available < count ) + off_t available = bufsize - off; + if ( (uintmax_t) available < (uintmax_t) count ) count = available; if ( !ctx->copy_to_dest(dest, buf + off, count) ) return -1; @@ -97,16 +96,17 @@ ssize_t UtilMemoryBuffer::pwrite(ioctx_t* ctx, const uint8_t* src, size_t count, off_t off) { ScopedLock lock(&filelock); - if ( !write ) { errno = EBADF; return -1; } - // TODO: Avoid having off + count overflow! - if ( bufsize < off + count ) - return 0; - if ( (uintmax_t) bufsize <= (uintmax_t) off ) - return -1; - size_t available = bufsize - off; - if ( available < count ) + if ( !write ) + return errno = EROFS, -1; + if ( bufsize < off ) + return errno = EFBIG, -1; + off_t available = bufsize - off; + if ( available == 0 && count ) + return errno = EFBIG, -1; + if ( (uintmax_t) available < (uintmax_t) count ) count = available; - ctx->copy_from_src(buf + off, src, count); + if ( !ctx->copy_from_src(buf + off, src, count) ) + return -1; return count; } diff --git a/kernel/fs/util.h b/kernel/fs/util.h index 813e4ca6..b84c3284 100644 --- a/kernel/fs/util.h +++ b/kernel/fs/util.h @@ -28,7 +28,7 @@ class UtilMemoryBuffer : public AbstractInode { public: UtilMemoryBuffer(dev_t dev, ino_t ino, uid_t owner, gid_t group, - mode_t mode, uint8_t* buf, size_t bufsize, + mode_t mode, uint8_t* buf, off_t bufsize, bool write = true, bool deletebuf = true); virtual ~UtilMemoryBuffer(); virtual int truncate(ioctx_t* ctx, off_t length); @@ -41,7 +41,7 @@ public: private: kthread_mutex_t filelock; uint8_t* buf; - size_t bufsize; + off_t bufsize; bool write; bool deletebuf; diff --git a/kernel/fs/zero.cpp b/kernel/fs/zero.cpp index 4405b9a7..10469096 100644 --- a/kernel/fs/zero.cpp +++ b/kernel/fs/zero.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen. + * Copyright (c) 2013, 2014, 2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -52,19 +52,10 @@ Zero::~Zero() { } -int Zero::truncate(ioctx_t* /*ctx*/, off_t /*length*/) -{ - return 0; -} - -off_t Zero::lseek(ioctx_t* /*ctx*/, off_t offset, int /*whence*/) -{ - return offset; -} - ssize_t Zero::read(ioctx_t* ctx, uint8_t* buf, size_t count) { - ctx->zero_dest(buf, count); + if ( !ctx->zero_dest(buf, count) ) + return -1; return (ssize_t) count; } diff --git a/kernel/fs/zero.h b/kernel/fs/zero.h index eea936f7..0730bf7b 100644 --- a/kernel/fs/zero.h +++ b/kernel/fs/zero.h @@ -29,8 +29,6 @@ class Zero : public AbstractInode public: Zero(dev_t dev, ino_t ino, uid_t owner, gid_t group, mode_t mode); virtual ~Zero(); - virtual int truncate(ioctx_t* ctx, off_t length); - virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence); virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count); virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off); virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count); diff --git a/kernel/include/sortix/kernel/descriptor.h b/kernel/include/sortix/kernel/descriptor.h index 2feebda9..facc2102 100644 --- a/kernel/include/sortix/kernel/descriptor.h +++ b/kernel/include/sortix/kernel/descriptor.h @@ -150,6 +150,7 @@ int LinkInodeInDir(ioctx_t* ctx, Ref dir, const char* name, Ref inode); Ref OpenDirContainingPath(ioctx_t* ctx, Ref from, const char* path, char** finalp); +size_t TruncateIOVec(struct iovec* iov, int iovcnt, off_t limit); } // namespace Sortix diff --git a/kernel/inode.cpp b/kernel/inode.cpp index 1dacb5e8..7b14bdf6 100644 --- a/kernel/inode.cpp +++ b/kernel/inode.cpp @@ -146,9 +146,7 @@ int AbstractInode::truncate(ioctx_t* /*ctx*/, off_t /*length*/) off_t AbstractInode::lseek(ioctx_t* /*ctx*/, off_t /*offset*/, int /*whence*/) { - if ( inode_type == INODE_TYPE_STREAM || inode_type == INODE_TYPE_TTY ) - return errno = ESPIPE, -1; - return errno = EBADF, -1; + return errno = ESPIPE, -1; } ssize_t AbstractInode::read(ioctx_t* ctx, uint8_t* buf, size_t count) diff --git a/kernel/io.cpp b/kernel/io.cpp index 9a1e305a..9c785278 100644 --- a/kernel/io.cpp +++ b/kernel/io.cpp @@ -838,6 +838,14 @@ int sys_mkpartition(int fd, off_t start, off_t length, int flags) Ref inner_inode = desc->vnode->inode; desc.Reset(); + if ( !S_ISBLK(inner_inode->type) && !S_ISREG(inner_inode->type) ) + return errno = EPERM, -1; + if ( start < 0 || length < 0 ) + return errno = EINVAL, -1; + off_t end; + if ( __builtin_add_overflow(start, length, &end) ) + return errno = EOVERFLOW, -1; + Ref partition(new Partition(inner_inode, start, length)); if ( !partition ) return -1; diff --git a/kernel/memorymanagement.cpp b/kernel/memorymanagement.cpp index 43525562..6fe51192 100644 --- a/kernel/memorymanagement.cpp +++ b/kernel/memorymanagement.cpp @@ -327,7 +327,7 @@ void* sys_mmap(void* addr_ptr, size_t size, int prot, int flags, int fd, if ( !(desc = process->GetDescriptor(fd)) ) return MAP_FAILED; // Verify that the file is seekable. - if ( desc->lseek(&ctx, 0, SEEK_CUR) < 0 ) + if ( S_ISCHR(desc->type) || desc->lseek(&ctx, 0, SEEK_END) < 0 ) return errno = ENODEV, MAP_FAILED; // Verify that we have read access to the file. if ( desc->read(&ctx, NULL, 0) != 0 ) diff --git a/kernel/partition.cpp b/kernel/partition.cpp index eb2421ea..2e3e81a2 100644 --- a/kernel/partition.cpp +++ b/kernel/partition.cpp @@ -19,6 +19,7 @@ #include +#include #include #include @@ -35,6 +36,10 @@ namespace Sortix { Partition::Partition(Ref inner_inode, off_t start, off_t length) { + assert(0 <= start); + assert(0 <= length); + assert(length <= OFF_MAX - start); + assert(S_ISBLK(inner_inode->type) || S_ISREG(inner_inode->type)); this->dev = (dev_t) this; this->ino = (ino_t) this; this->type = inner_inode->type; @@ -62,33 +67,48 @@ int Partition::truncate(ioctx_t* /*ctx*/, off_t new_length) off_t Partition::lseek(ioctx_t* /*ctx*/, off_t offset, int whence) { - if ( whence == SEEK_SET ) - return offset; - if ( whence == SEEK_END ) - // TODO: Avoid underflow and overflow! - return length + offset; + if ( whence == SEEK_END && offset == 0 ) + return length; return errno = EINVAL, -1; } ssize_t Partition::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off) { + if ( off < 0 ) + return errno = EINVAL, -1; if ( length <= off ) return 0; off_t available = length - off; if ( (uintmax_t) available < (uintmax_t) count ) count = available; - return inner_inode->pread(ctx, buf, count, start + off); + if ( count == 0 ) + return 0; + off_t final_offset; + if ( __builtin_add_overflow(start, off, &final_offset) ) + return errno = EOVERFLOW, -1; + assert(start <= final_offset); + assert(final_offset < start + length); + return inner_inode->pread(ctx, buf, count, final_offset); } ssize_t Partition::pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count, off_t off) { + if ( off < 0 ) + return errno = EINVAL, -1; if ( length <= off ) - return 0; + return count ? (errno = EFBIG, -1) : 0; off_t available = length - off; if ( (uintmax_t) available < (uintmax_t) count ) count = available; - return inner_inode->pwrite(ctx, buf, count, start + off); + if ( count == 0 ) + return 0; + off_t final_offset; + if ( __builtin_add_overflow(start, off, &final_offset) ) + return errno = EOVERFLOW, -1; + assert(start <= final_offset); + assert(final_offset < start + length); + return inner_inode->pwrite(ctx, buf, count, final_offset); } int Partition::stat(ioctx_t* ctx, struct stat* st) diff --git a/kernel/pipe.cpp b/kernel/pipe.cpp index ea1e7e3a..f7ba4d41 100644 --- a/kernel/pipe.cpp +++ b/kernel/pipe.cpp @@ -205,6 +205,8 @@ ssize_t PipeChannel::recvmsg_internal(ioctx_t* ctx, struct msghdr* msg, return errno = EINTR, -1; ssize_t so_far = 0; size_t peeked = 0; + if ( SSIZE_MAX < TruncateIOVec(msg->msg_iov, msg->msg_iovlen, SSIZE_MAX) ) + return errno = EINVAL, -1; int iov_i = 0; size_t iov_offset = 0; while ( iov_i < msg->msg_iovlen && so_far < SSIZE_MAX ) @@ -336,6 +338,8 @@ ssize_t PipeChannel::sendmsg_internal(ioctx_t* ctx, const struct msghdr* msg, if ( !lock.IsAcquired() ) return errno = EINTR, -1; sender_system_tid = this_thread->system_tid; + if ( SSIZE_MAX < TruncateIOVec(msg->msg_iov, msg->msg_iovlen, SSIZE_MAX) ) + return errno = EINVAL, -1; ssize_t so_far = 0; int iov_i = 0; size_t iov_offset = 0;