mirror of
https://github.com/apache/nuttx.git
synced 2026-09-05 08:32:59 +00:00
fs/inode: canonicalize path before inode search to fix .. resolution
Add _inode_canonicalize() to remove '.' and '..' segments from the
absolute path before the inode tree traversal begins. This fixes the
case where paths containing '..' that resolve back to a mountpoint
root (e.g., /tmp/subdir/..) were not being handed to the filesystem.
Previously, _compute_path_depth() returned 0 for such paths, causing
the VFS to skip the mountpoint and attempt to find 'subdir' in the
pseudo filesystem -- which fails with ENOTDIR.
With canonicalization, /tmp/subdir/.. becomes /tmp before the search,
so the mountpoint is correctly matched. This fixes chdir('..'),
stat('../..'), opendir('../..'), and similar operations from within
mountpoint subdirectories.
The implementation uses an in-place two-pointer algorithm with no
additional stack allocation, safe for NuttX's small kernel stacks.
Signed-off-by: yukangzhi <yukangzhi@xiaomi.com>
This commit is contained in:
parent
e396baf06f
commit
67cd42677b
1 changed files with 126 additions and 3 deletions
|
|
@ -48,6 +48,7 @@ static int _inode_linktarget(FAR struct inode *inode,
|
|||
#endif
|
||||
static int _inode_search(FAR struct inode_search_s *desc);
|
||||
static FAR const char *_inode_getcwd(void);
|
||||
static int _inode_canonicalize(FAR char *path);
|
||||
|
||||
/****************************************************************************
|
||||
* Public Data
|
||||
|
|
@ -240,6 +241,103 @@ static int _compute_path_depth(FAR const char *path)
|
|||
return depth;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: _inode_canonicalize
|
||||
*
|
||||
* Description:
|
||||
* Remove "." and ".." segments from an absolute path in-place.
|
||||
* The path MUST start with '/'. Returns -EINVAL if ".." attempts
|
||||
* to ascend beyond the root directory, or -ENAMETOOLONG if the
|
||||
* canonicalized result is >= PATH_MAX bytes.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int _inode_canonicalize(FAR char *path)
|
||||
{
|
||||
/* Skip the initial '/' -- caller guarantees absolute path */
|
||||
|
||||
FAR char *src = path + 1;
|
||||
FAR char *dst = path + 1;
|
||||
|
||||
while (*src != '\0')
|
||||
{
|
||||
/* Skip duplicate slashes */
|
||||
|
||||
if (*src == '/')
|
||||
{
|
||||
src++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check for "." (current directory) */
|
||||
|
||||
if (src[0] == '.' && (src[1] == '/' || src[1] == '\0'))
|
||||
{
|
||||
src += (src[1] == '/') ? 2 : 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check for ".." (parent directory) */
|
||||
|
||||
if (src[0] == '.' && src[1] == '.' &&
|
||||
(src[2] == '/' || src[2] == '\0'))
|
||||
{
|
||||
/* Cannot go above root */
|
||||
|
||||
if (dst <= path + 1)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Remove trailing slash first */
|
||||
|
||||
dst--;
|
||||
|
||||
/* Scan backward to find the previous '/' */
|
||||
|
||||
while (dst > path + 1 && *(dst - 1) != '/')
|
||||
{
|
||||
dst--;
|
||||
}
|
||||
|
||||
src += (src[2] == '/') ? 3 : 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Regular path component: copy until end of segment (including '/') */
|
||||
|
||||
do
|
||||
{
|
||||
if (dst != src)
|
||||
{
|
||||
*dst = *src;
|
||||
}
|
||||
|
||||
dst++;
|
||||
src++;
|
||||
}
|
||||
while (*src != '\0' && *(src - 1) != '/');
|
||||
}
|
||||
|
||||
/* Remove trailing slash (unless root "/") */
|
||||
|
||||
if (dst > path + 1 && *(dst - 1) == '/')
|
||||
{
|
||||
dst--;
|
||||
}
|
||||
|
||||
*dst = '\0';
|
||||
|
||||
/* After canonicalization, check if the resolved path exceeds PATH_MAX */
|
||||
|
||||
if ((dst - path) >= PATH_MAX)
|
||||
{
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: _inode_checkpath
|
||||
****************************************************************************/
|
||||
|
|
@ -313,9 +411,9 @@ static int _inode_search(FAR struct inode_search_s *desc)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Convert the relative path to the absolute path */
|
||||
/* Ensure we have a writable buffer for path manipulation */
|
||||
|
||||
if (*desc->path != '/')
|
||||
if (desc->buffer == NULL)
|
||||
{
|
||||
desc->buffer = lib_get_tempbuffer(PATH_MAX);
|
||||
if (desc->buffer == NULL)
|
||||
|
|
@ -323,10 +421,35 @@ static int _inode_search(FAR struct inode_search_s *desc)
|
|||
return -ENOMEM;
|
||||
}
|
||||
|
||||
snprintf(desc->buffer, PATH_MAX, "%s/%s", _inode_getcwd(), desc->path);
|
||||
/* Convert relative path to absolute, or just copy absolute path.
|
||||
* Use desc->path directly (points to caller's string) as source
|
||||
* to avoid overlap with desc->buffer.
|
||||
*/
|
||||
|
||||
if (*desc->path != '/')
|
||||
{
|
||||
snprintf(desc->buffer, PATH_MAX, "%s/%s",
|
||||
_inode_getcwd(), desc->path);
|
||||
}
|
||||
else
|
||||
{
|
||||
strlcpy(desc->buffer, desc->path, PATH_MAX);
|
||||
}
|
||||
|
||||
desc->path = desc->buffer;
|
||||
}
|
||||
|
||||
/* Canonicalize the path to remove "." and ".." segments. This ensures
|
||||
* that mountpoint relpath never contains ".." which most filesystems
|
||||
* (tmpfs, romfs, etc.) cannot resolve.
|
||||
*/
|
||||
|
||||
ret = _inode_canonicalize(desc->buffer);
|
||||
if (ret < 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
name = desc->path;
|
||||
ret = -ENOENT;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue