On 64-bit platforms /dev/mem exceeds the size supported by loff_t and needs special treatment within the rest of FS API. Specifically lseek() needs to be modified to make sure it does the right thing. Prievious attempt at fixing this issue by using IS_ERR_VALUE() e10efc5080 ("fs: fix memory access via /dev/mem for MIPS64") doesn't really work 100% on 64-bit platforms, becuase it still leaves out a number of perfectly valid offsets (e.g. "md 0xffffffffffffff00" doesn't work) . Moreso it breaks lseek() on 32-bit platforms, since IS_ERR_VALUE will retrurn true for any offset that is >= (unsigned long) -MAX_ERRNO. In order to fix this issue on both 32 and 64 bit platforms, introduce DEVFS_UNBOUNDED flag that cdevs can use to denote that they span all 64-bit address space and effectively have not limits. To propagate that info to FS layer, add "unbounded" boolean to FILE. As a last step modify lseek() to be aware of that field and do the right checks in that case. Note, that since loff_t has no problem covering all of address space on 32-bit platforms, DEVFS_UNBOUNDED is defined to expand into 0 and not be settable there. Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> --- drivers/misc/mem.c | 1 + fs/devfs.c | 2 ++ fs/fs.c | 29 +++++++++++++++++++++++++---- include/driver.h | 1 + include/fs.h | 1 + 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/misc/mem.c b/drivers/misc/mem.c index d829af724..b27865b9e 100644 --- a/drivers/misc/mem.c +++ b/drivers/misc/mem.c @@ -21,6 +21,7 @@ static int mem_probe(struct device_d *dev) cdev = xzalloc(sizeof (*cdev)); dev->priv = cdev; + cdev->flags = DEVFS_UNBOUNDED; cdev->name = (char*)dev->resource[0].name; cdev->size = min_t(unsigned long long, resource_size(&dev->resource[0]), S64_MAX); diff --git a/fs/devfs.c b/fs/devfs.c index 81ae2c25a..68a5b7f23 100644 --- a/fs/devfs.c +++ b/fs/devfs.c @@ -120,6 +120,8 @@ static int devfs_open(struct device_d *_dev, FILE *f, const char *filename) struct cdev *cdev = node->cdev; int ret; + + f->unbounded = cdev->flags & DEVFS_UNBOUNDED; f->size = cdev->flags & DEVFS_IS_CHARACTER_DEV ? FILE_SIZE_STREAM : cdev->size; f->priv = cdev; diff --git a/fs/fs.c b/fs/fs.c index 9372b9981..48ca382df 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -422,20 +422,41 @@ loff_t lseek(int fildes, loff_t offset, int whence) switch (whence) { case SEEK_SET: pos = offset; + if (unlikely(f->unbounded)) + break; if (f->size != FILE_SIZE_STREAM && offset > f->size) goto out; - if (IS_ERR_VALUE(offset)) + if (offset < 0) goto out; break; case SEEK_CUR: pos = f->pos + offset; + if (unlikely(f->unbounded)) { + /* + * Check that we won't wrap around when + * seeking + */ + u64 p, o; + + p = f->pos; + o = offset; + + if (p > U64_MAX - o) + goto out; + break; + } + if (f->size != FILE_SIZE_STREAM && pos > f->size) goto out; break; case SEEK_END: - pos = f->size + offset; - if (offset > 0) - goto out; + if (unlikely(f->unbounded)) { + pos = U64_MAX - (u64)offset; + } else { + pos = f->size + offset; + if (offset > 0) + goto out; + } break; default: goto out; diff --git a/include/driver.h b/include/driver.h index 3d9970df5..39bce0414 100644 --- a/include/driver.h +++ b/include/driver.h @@ -501,6 +501,7 @@ int cdev_erase(struct cdev *cdev, loff_t count, loff_t offset); #define DEVFS_PARTITION_READONLY (1U << 1) #define DEVFS_IS_CHARACTER_DEV (1 << 3) #define DEVFS_PARTITION_FROM_TABLE (1 << 4) +#define DEVFS_UNBOUNDED (IS_ENABLED(CONFIG_CPU_64) << 5) struct cdev *devfs_add_partition(const char *devname, loff_t offset, loff_t size, unsigned int flags, const char *name); diff --git a/include/fs.h b/include/fs.h index f1514afa9..5d1510838 100644 --- a/include/fs.h +++ b/include/fs.h @@ -31,6 +31,7 @@ typedef struct filep { /* private fields. Mapping between FILE and filedescriptor number */ int no; char in_use; + bool unbounded; struct inode *f_inode; struct dentry *dentry; -- 2.20.1 _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox