fs: ubifs: fix bugs involving symlinks in ubifs_findfile

When encountering a symlink pointing to an absolute path, ubifs_findfile
would return the target of the symlink as the result instead of resolving
any following components in the original path. Fix it by following the
same code path that is used for relative paths except that we set the
next inode to the root if we see a leading slash.

The existing code used memcpy and sprintf to copy the symlink target
into a fixed size stack buffer and was therefore vulnerable to buffer
overflows with a sufficiently long symlink target. Fix it by using a
heap buffer for the temporary path during path resolution.

Signed-off-by: Peter Collingbourne <peter@pcc.me.uk>
Fixes: 9d7952e4c6 ("ubifs: Add support for looking up directory and relative symlinks")
This commit is contained in:
Peter Collingbourne
2026-05-05 03:38:16 -07:00
committed by Tom Rini
parent 22a285380e
commit d5888d509c

View File

@@ -505,26 +505,32 @@ static unsigned long ubifs_findfile(struct super_block *sb, char *filename)
{ {
int ret; int ret;
char *next; char *next;
char fpath[128]; char *buf;
char symlinkpath[128]; char *name;
char *name = fpath;
unsigned long root_inum = 1; unsigned long root_inum = 1;
unsigned long inum; unsigned long inum;
int symlink_count = 0; /* Don't allow symlink recursion */ int symlink_count = 0; /* Don't allow symlink recursion */
char link_name[64]; size_t filenamelen;
strcpy(fpath, filename);
/* Remove all leading slashes */ /* Remove all leading slashes */
while (*name == '/') while (*filename == '/')
name++; filename++;
filenamelen = strlen(filename);
buf = kmalloc(filenamelen + 1, GFP_NOFS);
if (!buf)
return -ENOMEM;
memcpy(buf, filename, filenamelen + 1);
name = buf;
/* /*
* Handle root-direcoty ('/') * Handle root-direcoty ('/')
*/ */
inum = root_inum; inum = root_inum;
if (!name || *name == '\0') if (!name || *name == '\0') {
kfree(buf);
return inum; return inum;
}
for (;;) { for (;;) {
struct inode *inode; struct inode *inode;
@@ -537,41 +543,53 @@ static unsigned long ubifs_findfile(struct super_block *sb, char *filename)
while (*next == '/') while (*next == '/')
*(next++) = '\0'; *(next++) = '\0';
} }
ret = ubifs_finddir(sb, name, root_inum, &inum); ret = ubifs_finddir(sb, name, root_inum, &inum);
if (!ret) if (!ret) {
kfree(buf);
return 0; return 0;
}
inode = ubifs_iget(sb, inum); inode = ubifs_iget(sb, inum);
if (!inode) if (!inode) {
kfree(buf);
return 0; return 0;
}
ui = ubifs_inode(inode); ui = ubifs_inode(inode);
if ((inode->i_mode & S_IFMT) == S_IFLNK) { if ((inode->i_mode & S_IFMT) == S_IFLNK) {
char buf[128]; size_t newbufsize;
char *newbuf;
char *linkdata = ui->data;
size_t linklen = ui->data_len;
/* We have some sort of symlink recursion, bail out */ /* We have some sort of symlink recursion, bail out */
if (symlink_count++ > 8) { if (symlink_count++ > 8) {
ubifs_iput(inode); ubifs_iput(inode);
printf("Symlink recursion, aborting\n"); printf("Symlink recursion, aborting\n");
kfree(buf);
return 0; return 0;
} }
memcpy(link_name, ui->data, ui->data_len);
link_name[ui->data_len] = '\0';
if (link_name[0] == '/') { while (linklen && *linkdata == '/') {
/* Absolute path, redo everything without /* Absolute path, i.e. relative to root. */
* the leading slash */
next = name = link_name + 1;
root_inum = 1; root_inum = 1;
ubifs_iput(inode); linkdata++;
continue; linklen--;
} }
/* Relative to cur dir */ newbufsize =
sprintf(buf, "%s/%s", linklen + 1 + (next ? strlen(next) : 0) + 1;
link_name, next == NULL ? "" : next); newbuf = kmalloc(newbufsize, GFP_NOFS);
memcpy(symlinkpath, buf, sizeof(buf)); if (!newbuf) {
next = name = symlinkpath; kfree(buf);
ubifs_iput(inode);
return -ENOMEM;
}
memcpy(newbuf, linkdata, linklen);
sprintf(newbuf + linklen, "/%s", next ?: "");
kfree(buf);
buf = newbuf;
name = newbuf;
ubifs_iput(inode); ubifs_iput(inode);
continue; continue;
} }
@@ -583,6 +601,7 @@ static unsigned long ubifs_findfile(struct super_block *sb, char *filename)
/* Found the node! */ /* Found the node! */
if (!next || *next == '\0') { if (!next || *next == '\0') {
ubifs_iput(inode); ubifs_iput(inode);
kfree(buf);
return inum; return inum;
} }
@@ -590,6 +609,7 @@ static unsigned long ubifs_findfile(struct super_block *sb, char *filename)
name = next; name = next;
} }
kfree(buf);
return 0; return 0;
} }