mirror of
https://github.com/torvalds/linux.git
synced 2024-11-14 16:12:02 +00:00
040bf9a717
Squashfs copied the passed string (name) into a temporary buffer to ensure it was NUL-terminated. This however is completely unnecessary as the string is already NUL-terminated. So remove the deprecated strncpy() by completely removing the string copy. The background behind this unnecessary string copy is that it dates back to the days when Squashfs was an out of kernel patch. The code deliberately did not assume the string was NUL-terminated in case in future this changed (due to kernel changes). This would mean the out of tree patches would be broken but still compile OK. Link: https://lkml.kernel.org/r/20240403183352.391308-1-phillip@squashfs.org.uk Signed-off-by: Phillip Lougher <phillip@squashfs.org.uk> Reviewed-by: Kees Cook <keescook@chromium.org> Reviewed-by: Justin Stitt <justinstitt@google.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
233 lines
6.7 KiB
C
233 lines
6.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Squashfs - a compressed read only filesystem for Linux
|
|
*
|
|
* Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007, 2008
|
|
* Phillip Lougher <phillip@squashfs.org.uk>
|
|
*
|
|
* namei.c
|
|
*/
|
|
|
|
/*
|
|
* This file implements code to do filename lookup in directories.
|
|
*
|
|
* Like inodes, directories are packed into compressed metadata blocks, stored
|
|
* in a directory table. Directories are accessed using the start address of
|
|
* the metablock containing the directory and the offset into the
|
|
* decompressed block (<block, offset>).
|
|
*
|
|
* Directories are organised in a slightly complex way, and are not simply
|
|
* a list of file names. The organisation takes advantage of the
|
|
* fact that (in most cases) the inodes of the files will be in the same
|
|
* compressed metadata block, and therefore, can share the start block.
|
|
* Directories are therefore organised in a two level list, a directory
|
|
* header containing the shared start block value, and a sequence of directory
|
|
* entries, each of which share the shared start block. A new directory header
|
|
* is written once/if the inode start block changes. The directory
|
|
* header/directory entry list is repeated as many times as necessary.
|
|
*
|
|
* Directories are sorted, and can contain a directory index to speed up
|
|
* file lookup. Directory indexes store one entry per metablock, each entry
|
|
* storing the index/filename mapping to the first directory header
|
|
* in each metadata block. Directories are sorted in alphabetical order,
|
|
* and at lookup the index is scanned linearly looking for the first filename
|
|
* alphabetically larger than the filename being looked up. At this point the
|
|
* location of the metadata block the filename is in has been found.
|
|
* The general idea of the index is ensure only one metadata block needs to be
|
|
* decompressed to do a lookup irrespective of the length of the directory.
|
|
* This scheme has the advantage that it doesn't require extra memory overhead
|
|
* and doesn't require much extra storage on disk.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/vfs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/dcache.h>
|
|
#include <linux/xattr.h>
|
|
|
|
#include "squashfs_fs.h"
|
|
#include "squashfs_fs_sb.h"
|
|
#include "squashfs_fs_i.h"
|
|
#include "squashfs.h"
|
|
#include "xattr.h"
|
|
|
|
/*
|
|
* Lookup name in the directory index, returning the location of the metadata
|
|
* block containing it, and the directory index this represents.
|
|
*
|
|
* If we get an error reading the index then return the part of the index
|
|
* (if any) we have managed to read - the index isn't essential, just
|
|
* quicker.
|
|
*/
|
|
static int get_dir_index_using_name(struct super_block *sb,
|
|
u64 *next_block, int *next_offset, u64 index_start,
|
|
int index_offset, int i_count, const char *name)
|
|
{
|
|
struct squashfs_sb_info *msblk = sb->s_fs_info;
|
|
int i, length = 0, err;
|
|
unsigned int size;
|
|
struct squashfs_dir_index *index;
|
|
|
|
TRACE("Entered get_dir_index_using_name, i_count %d\n", i_count);
|
|
|
|
index = kmalloc(sizeof(*index) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
|
|
if (index == NULL) {
|
|
ERROR("Failed to allocate squashfs_dir_index\n");
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < i_count; i++) {
|
|
err = squashfs_read_metadata(sb, index, &index_start,
|
|
&index_offset, sizeof(*index));
|
|
if (err < 0)
|
|
break;
|
|
|
|
|
|
size = le32_to_cpu(index->size) + 1;
|
|
if (size > SQUASHFS_NAME_LEN)
|
|
break;
|
|
|
|
err = squashfs_read_metadata(sb, index->name, &index_start,
|
|
&index_offset, size);
|
|
if (err < 0)
|
|
break;
|
|
|
|
index->name[size] = '\0';
|
|
|
|
if (strcmp(index->name, name) > 0)
|
|
break;
|
|
|
|
length = le32_to_cpu(index->index);
|
|
*next_block = le32_to_cpu(index->start_block) +
|
|
msblk->directory_table;
|
|
}
|
|
|
|
*next_offset = (length + *next_offset) % SQUASHFS_METADATA_SIZE;
|
|
kfree(index);
|
|
|
|
out:
|
|
/*
|
|
* Return index (f_pos) of the looked up metadata block. Translate
|
|
* from internal f_pos to external f_pos which is offset by 3 because
|
|
* we invent "." and ".." entries which are not actually stored in the
|
|
* directory.
|
|
*/
|
|
return length + 3;
|
|
}
|
|
|
|
|
|
static struct dentry *squashfs_lookup(struct inode *dir, struct dentry *dentry,
|
|
unsigned int flags)
|
|
{
|
|
const unsigned char *name = dentry->d_name.name;
|
|
int len = dentry->d_name.len;
|
|
struct inode *inode = NULL;
|
|
struct squashfs_sb_info *msblk = dir->i_sb->s_fs_info;
|
|
struct squashfs_dir_header dirh;
|
|
struct squashfs_dir_entry *dire;
|
|
u64 block = squashfs_i(dir)->start + msblk->directory_table;
|
|
int offset = squashfs_i(dir)->offset;
|
|
int err, length;
|
|
unsigned int dir_count, size;
|
|
|
|
TRACE("Entered squashfs_lookup [%llx:%x]\n", block, offset);
|
|
|
|
dire = kmalloc(sizeof(*dire) + SQUASHFS_NAME_LEN + 1, GFP_KERNEL);
|
|
if (dire == NULL) {
|
|
ERROR("Failed to allocate squashfs_dir_entry\n");
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
|
|
if (len > SQUASHFS_NAME_LEN) {
|
|
err = -ENAMETOOLONG;
|
|
goto failed;
|
|
}
|
|
|
|
length = get_dir_index_using_name(dir->i_sb, &block, &offset,
|
|
squashfs_i(dir)->dir_idx_start,
|
|
squashfs_i(dir)->dir_idx_offset,
|
|
squashfs_i(dir)->dir_idx_cnt, name);
|
|
|
|
while (length < i_size_read(dir)) {
|
|
/*
|
|
* Read directory header.
|
|
*/
|
|
err = squashfs_read_metadata(dir->i_sb, &dirh, &block,
|
|
&offset, sizeof(dirh));
|
|
if (err < 0)
|
|
goto read_failure;
|
|
|
|
length += sizeof(dirh);
|
|
|
|
dir_count = le32_to_cpu(dirh.count) + 1;
|
|
|
|
if (dir_count > SQUASHFS_DIR_COUNT)
|
|
goto data_error;
|
|
|
|
while (dir_count--) {
|
|
/*
|
|
* Read directory entry.
|
|
*/
|
|
err = squashfs_read_metadata(dir->i_sb, dire, &block,
|
|
&offset, sizeof(*dire));
|
|
if (err < 0)
|
|
goto read_failure;
|
|
|
|
size = le16_to_cpu(dire->size) + 1;
|
|
|
|
/* size should never be larger than SQUASHFS_NAME_LEN */
|
|
if (size > SQUASHFS_NAME_LEN)
|
|
goto data_error;
|
|
|
|
err = squashfs_read_metadata(dir->i_sb, dire->name,
|
|
&block, &offset, size);
|
|
if (err < 0)
|
|
goto read_failure;
|
|
|
|
length += sizeof(*dire) + size;
|
|
|
|
if (name[0] < dire->name[0])
|
|
goto exit_lookup;
|
|
|
|
if (len == size && !strncmp(name, dire->name, len)) {
|
|
unsigned int blk, off, ino_num;
|
|
long long ino;
|
|
blk = le32_to_cpu(dirh.start_block);
|
|
off = le16_to_cpu(dire->offset);
|
|
ino_num = le32_to_cpu(dirh.inode_number) +
|
|
(short) le16_to_cpu(dire->inode_number);
|
|
ino = SQUASHFS_MKINODE(blk, off);
|
|
|
|
TRACE("calling squashfs_iget for directory "
|
|
"entry %s, inode %x:%x, %d\n", name,
|
|
blk, off, ino_num);
|
|
|
|
inode = squashfs_iget(dir->i_sb, ino, ino_num);
|
|
goto exit_lookup;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit_lookup:
|
|
kfree(dire);
|
|
return d_splice_alias(inode, dentry);
|
|
|
|
data_error:
|
|
err = -EIO;
|
|
|
|
read_failure:
|
|
ERROR("Unable to read directory block [%llx:%x]\n",
|
|
squashfs_i(dir)->start + msblk->directory_table,
|
|
squashfs_i(dir)->offset);
|
|
failed:
|
|
kfree(dire);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
|
|
const struct inode_operations squashfs_dir_inode_ops = {
|
|
.lookup = squashfs_lookup,
|
|
.listxattr = squashfs_listxattr
|
|
};
|