diff --git a/Documentation/components/filesystem/littlefs.rst b/Documentation/components/filesystem/littlefs.rst index 8e07e07481896..35c042cc8f7a0 100644 --- a/Documentation/components/filesystem/littlefs.rst +++ b/Documentation/components/filesystem/littlefs.rst @@ -18,7 +18,7 @@ well as file operations (``fopen()``, ``fclose()``, etc). `_. -..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. diff --git a/fs/inode/inode.h b/fs/inode/inode.h index 3735d9a566cb7..f3078cadbdc6b 100644 --- a/fs/inode/inode.h +++ b/fs/inode/inode.h @@ -455,6 +455,12 @@ int inode_checkperm(FAR struct inode *inode, int oflags); int inode_checkdirperm(FAR struct inode *dir, int amode); +#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 * diff --git a/fs/littlefs/lfs_vfs.c b/fs/littlefs/lfs_vfs.c index 1a4208e3364c7..3982544025780 100644 --- a/fs/littlefs/lfs_vfs.c +++ b/fs/littlefs/lfs_vfs.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -38,7 +40,6 @@ #include #include -#include #include #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;