fs/littlefs: Enforce open permissions and set create ownership

Check open access against file mode and owner via fs_checkmode(), verify
parent directory permissions on create, and assign creator uid/gid to
newly created files.

Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
This commit is contained in:
Abhishek Mishra 2026-06-17 16:59:41 +00:00 committed by Xiang Xiao
parent 0e662be70a
commit 34dabfc4e7
3 changed files with 265 additions and 2 deletions

View file

@ -18,7 +18,7 @@ well as file operations (``fopen()``, ``fclose()``, etc).
<https://github.com/apache/nuttx/issues/15840>`_.
..note::
.. note::
If your littlefs setup is experiencing crashes when you boot, try
troubleshooting by tweaking the ``BLOCK_SIZE_FACTOR`` options in Kconfig. A
@ -29,3 +29,31 @@ well as file operations (``fopen()``, ``fclose()``, etc).
The littlefs support on NuttX only works with mtd drivers, for storage
devices such as flash chips, SD cards and eMMC. Performance on SD cards and
eMMC devices is worse than flash.
User identity and permissions
=============================
When both ``CONFIG_FS_PERMISSION`` and ``CONFIG_FS_LITTLEFS_ATTR_UPDATE`` are
enabled, littlefs stores owner, group, and mode in per-file custom attributes.
This requires :ref:`user-identity` (``CONFIG_SCHED_USER_IDENTITY``).
**Metadata.** ``chmod``/``chown`` (via ``chstat``) update the stored mode,
UID, and GID. ``stat`` and ``ls -l`` read them back.
**Creation.** New files and directories are created with the effective UID and
GID of the creating task.
**Open checks.** ``open()`` enforces POSIX permission bits using the caller's
effective identity:
* Existing files: each path component must grant search (``X_OK``) permission;
the final component must grant the access requested by ``oflags``.
* ``O_CREAT`` on a non-existent file: the parent directory must grant write and
search permission.
**Directory reads.** ``opendir()`` requires read and search permission on the
target directory.
Files created before permission support was enabled, or without stored
attributes, default to mode ``0777`` until ``chmod``/``chown`` sets explicit
metadata.

View file

@ -461,6 +461,12 @@ int fs_open_amode(int oflags);
int fs_checkopenperm(uid_t owner, gid_t group, mode_t mode, int oflags);
#endif
#ifdef CONFIG_FS_PERMISSION
int fs_checkmode(uid_t owner, gid_t group, mode_t mode, int amode);
int fs_open_amode(int oflags);
int fs_checkopenperm(uid_t owner, gid_t group, mode_t mode, int oflags);
#endif
/****************************************************************************
* Name: foreach_inode
*

View file

@ -30,6 +30,8 @@
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <nuttx/crc16.h>
#include <nuttx/fs/fs.h>
@ -38,7 +40,6 @@
#include <nuttx/mtd/mtd.h>
#include <nuttx/mutex.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include "inode/inode.h"
@ -337,6 +338,193 @@ static FAR const char *littlefs_convert_path(FAR const char *path)
return path;
}
#if defined(CONFIG_FS_PERMISSION) && defined(CONFIG_FS_LITTLEFS_ATTR_UPDATE)
/****************************************************************************
* Name: littlefs_statbuf_locked
****************************************************************************/
static int littlefs_statbuf_locked(FAR struct littlefs_mountpt_s *fs,
FAR const char *relpath,
FAR struct stat *buf)
{
struct lfs_info info;
struct littlefs_attr_s attr;
int ret;
memset(buf, 0, sizeof(*buf));
ret = lfs_stat(&fs->lfs, relpath, &info);
if (ret < 0)
{
return littlefs_convert_result(ret);
}
ret = littlefs_convert_result(lfs_getattr(&fs->lfs, relpath, 0,
&attr, sizeof(attr)));
if (ret < 0)
{
if (ret != -ENODATA)
{
return ret;
}
memset(&attr, 0, sizeof(attr));
attr.at_mode = S_IRWXG | S_IRWXU | S_IRWXO;
}
buf->st_mode = attr.at_mode;
buf->st_uid = attr.at_uid;
buf->st_gid = attr.at_gid;
buf->st_atim.tv_sec = attr.at_atim / 1000000000ull;
buf->st_atim.tv_nsec = attr.at_atim % 1000000000ull;
buf->st_mtim.tv_sec = attr.at_mtim / 1000000000ull;
buf->st_mtim.tv_nsec = attr.at_mtim % 1000000000ull;
buf->st_ctim.tv_sec = attr.at_ctim / 1000000000ull;
buf->st_ctim.tv_nsec = attr.at_ctim % 1000000000ull;
buf->st_blksize = fs->cfg.block_size;
if (info.type == LFS_TYPE_REG)
{
buf->st_mode |= S_IFREG;
buf->st_size = info.size;
}
else
{
buf->st_mode |= S_IFDIR;
buf->st_size = 0;
}
buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / buf->st_blksize;
return OK;
}
/****************************************************************************
* Name: littlefs_check_pathperm_locked
****************************************************************************/
static int littlefs_check_pathperm_locked(FAR struct littlefs_mountpt_s *fs,
FAR const char *relpath,
int final_amode)
{
struct stat st;
char subpath[PATH_MAX];
size_t begin = 0;
size_t end;
int ret;
while (relpath[begin] == '/')
{
begin++;
}
if (relpath[begin] == '\0')
{
return OK;
}
for (end = begin; ; end++)
{
if (relpath[end] != '\0' && relpath[end] != '/')
{
continue;
}
if (relpath[end] == '\0')
{
break;
}
if (end - begin >= PATH_MAX)
{
return -ENAMETOOLONG;
}
memcpy(subpath, relpath + begin, end - begin);
subpath[end - begin] = '\0';
ret = littlefs_statbuf_locked(fs, subpath, &st);
if (ret < 0)
{
return ret;
}
ret = fs_checkmode(st.st_uid, st.st_gid, st.st_mode, X_OK);
if (ret < 0)
{
return ret;
}
}
/* Check permission on the final path component */
ret = littlefs_statbuf_locked(fs, &relpath[begin], &st);
if (ret < 0)
{
return ret;
}
return fs_checkmode(st.st_uid, st.st_gid, st.st_mode, final_amode);
}
/****************************************************************************
* Name: littlefs_check_openperm_locked
****************************************************************************/
static int littlefs_check_openperm_locked(FAR struct littlefs_mountpt_s *fs,
FAR const char *relpath,
int oflags)
{
return littlefs_check_pathperm_locked(fs, relpath,
fs_open_amode(oflags));
}
/****************************************************************************
* Name: littlefs_check_parentperm_locked
****************************************************************************/
static int littlefs_check_parentperm_locked(
FAR struct littlefs_mountpt_s *fs,
FAR const char *relpath, int amode)
{
FAR const char *slash;
char parent[PATH_MAX + 1];
struct stat st;
size_t pathlen;
int ret;
slash = strrchr(relpath, '/');
if (slash == NULL || slash == relpath)
{
ret = littlefs_statbuf_locked(fs, "/", &st);
if (ret < 0)
{
return ret;
}
return fs_checkmode(st.st_uid, st.st_gid, st.st_mode, amode);
}
pathlen = slash - relpath;
if (pathlen > PATH_MAX)
{
return -ENAMETOOLONG;
}
memcpy(parent, relpath, pathlen);
parent[pathlen] = '\0';
ret = littlefs_statbuf_locked(fs, parent, &st);
if (ret < 0)
{
return ret;
}
return fs_checkmode(st.st_uid, st.st_gid, st.st_mode, amode);
}
#endif /* CONFIG_FS_PERMISSION && CONFIG_FS_LITTLEFS_ATTR_UPDATE */
/****************************************************************************
* Name: littlefs_open
****************************************************************************/
@ -347,6 +535,9 @@ static int littlefs_open(FAR struct file *filep, FAR const char *relpath,
FAR struct littlefs_mountpt_s *fs;
FAR struct littlefs_file_s *priv;
FAR struct inode *inode;
#if defined(CONFIG_FS_PERMISSION) && defined(CONFIG_FS_LITTLEFS_ATTR_UPDATE)
struct lfs_info info;
#endif
int ret;
/* Get the mountpoint inode reference from the file structure and the
@ -377,6 +568,27 @@ static int littlefs_open(FAR struct file *filep, FAR const char *relpath,
/* Try to open the file */
relpath = littlefs_convert_path(relpath);
#if defined(CONFIG_FS_PERMISSION) && defined(CONFIG_FS_LITTLEFS_ATTR_UPDATE)
ret = lfs_stat(&fs->lfs, relpath, &info);
if (ret == 0)
{
ret = littlefs_check_openperm_locked(fs, relpath, oflags);
if (ret < 0)
{
goto errout;
}
}
else if (ret == LFS_ERR_NOENT && (oflags & O_CREAT) != 0)
{
ret = littlefs_check_parentperm_locked(fs, relpath, W_OK | X_OK);
if (ret < 0)
{
goto errout;
}
}
#endif
oflags = littlefs_convert_oflags(oflags);
if (fs->readonly)
{
@ -405,6 +617,10 @@ static int littlefs_open(FAR struct file *filep, FAR const char *relpath,
clock_gettime(CLOCK_REALTIME, &time);
memset(&attr, 0, sizeof(attr));
attr.at_mode = mode;
#ifdef CONFIG_FS_PERMISSION
attr.at_uid = geteuid();
attr.at_gid = getegid();
#endif
attr.at_ctim = 1000000000ull * time.tv_sec + time.tv_nsec;
attr.at_atim = attr.at_ctim;
attr.at_mtim = attr.at_ctim;
@ -1019,6 +1235,15 @@ static int littlefs_opendir(FAR struct inode *mountpt,
/* Call the LFS's opendir function */
relpath = littlefs_convert_path(relpath);
#if defined(CONFIG_FS_PERMISSION) && defined(CONFIG_FS_LITTLEFS_ATTR_UPDATE)
ret = littlefs_check_pathperm_locked(fs, relpath, R_OK | X_OK);
if (ret < 0)
{
goto errout;
}
#endif
ret = littlefs_convert_result(lfs_dir_open(&fs->lfs, &ldir->dir, relpath));
if (ret < 0)
{
@ -1740,6 +1965,10 @@ static int littlefs_mkdir(FAR struct inode *mountpt, FAR const char *relpath,
clock_gettime(CLOCK_REALTIME, &time);
memset(&attr, 0, sizeof(attr));
attr.at_mode = mode;
#ifdef CONFIG_FS_PERMISSION
attr.at_uid = geteuid();
attr.at_gid = getegid();
#endif
attr.at_ctim = 1000000000ull * time.tv_sec + time.tv_nsec;
attr.at_atim = attr.at_ctim;
attr.at_mtim = attr.at_ctim;