[PATCH 2/2] fs: __write: fix EOF growth checks for negative file sizes
Stefan Kerkmann
s.kerkmann at pengutronix.de
Thu Sep 3 09:29:14 PDT 2026
__write() grows the file when the write end would pass EOF, but based
the decision on the same target dependent mixed sign comparison that
__read() had: f->f_pos + count > f->f_size compares the signed 64-bit
position and size (loff_t) against count (size_t), which is 32-bit on
32-bit arches and 64-bit on 64-bit arches. For negative file sizes
except for the FILE_SIZE_STREAM sentinel this is broken:
- On 32-bit arches count is converted to the signed 64-bit type of
f->f_pos, so for e.g. f->f_size = -512 the comparison evaluated true.
fsdev_truncate() was then called for the corrupted file size,
attempting to grow the file to f->f_pos + count.
- On 64-bit arches size_t cannot be represented by signed 64-bit, so the
usual arithmetic conversions turned the whole comparison unsigned:
f->f_size = -512 was reinterpreted as a value near 2^64, the
comparison stayed false, the file was never grown and the write
proceeded unclamped against the bogus size.
Additionally, when writing at a position past the end of the file
(reachable via pwrite() with a large offset) and fsdev_truncate() failed
with -ENOSPC, the fallback count f->f_size - f->f_pos was negative and
wrapped to a huge value in the unsigned count.
__write() now rejects negative file sizes with -EINVAL, like __read()
does. The write end f->f_pos + count is computed in u64, making the
growth check target independent, and extending the file beyond
MAX_LFS_FILESIZE is rejected with -EFBIG. On -ENOSPC the write is now
limited to the bytes remaining until EOF, or aborted when f->f_pos is at
or past the end of the file, instead of wrapping the negative remainder.
Signed-off-by: Stefan Kerkmann <s.kerkmann at pengutronix.de>
---
fs/fs.c | 42 +++++++++++++++++++++++++++++++-----------
1 file changed, 31 insertions(+), 11 deletions(-)
diff --git a/fs/fs.c b/fs/fs.c
index a8f2b78294..3803decc2a 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -482,7 +482,10 @@ EXPORT_SYMBOL(read);
static ssize_t __write(struct file *f, const void *buf, size_t count)
{
+ u64 size = (u64)f->f_size;
+ u64 pos = (u64)f->f_pos;
struct fs_driver *fsdrv;
+ u64 end;
int ret;
fsdrv = f->fsdev->driver;
@@ -495,18 +498,35 @@ static ssize_t __write(struct file *f, const void *buf, size_t count)
if (fsdrv != ramfs_driver)
assert_command_context();
- if (f->f_size != FILE_SIZE_STREAM && f->f_pos + count > f->f_size) {
- ret = fsdev_truncate(f, f->f_pos + count);
- if (ret) {
- if (ret == -EPERM)
- ret = -ENOSPC;
- if (ret != -ENOSPC)
+ if (f->f_size != FILE_SIZE_STREAM) {
+ if (f->f_size < 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Writing past the end of the file requires growing it first */
+ end = pos + count;
+ if (end > size) {
+ /* New file size must be representable as loff_t */
+ if (end > (u64)MAX_LFS_FILESIZE ||
+ (f->f_pos >= 0 && end < pos)) {
+ ret = -EFBIG;
goto out;
- count = f->f_size - f->f_pos;
- if (!count)
- goto out;
- } else {
- f->f_size = f->f_pos + count;
+ }
+
+ ret = fsdev_truncate(f, end);
+ if (ret) {
+ if (ret == -EPERM)
+ ret = -ENOSPC;
+ if (ret != -ENOSPC)
+ goto out;
+ /* Truncate failed; write what fits into the file */
+ count = pos < size ? size - pos : 0;
+ if (!count)
+ goto out;
+ } else {
+ f->f_size = end;
+ }
}
}
--
2.47.3
More information about the barebox
mailing list