ardupilot/libraries/AP_Filesystem/AP_Filesystem_ROMFS.cpp
Thomas Watson 9f0cbd881d AP_Filesystem: ROMFS: fix open race conditions
Lua opens scripts to load them into memory, then the logger opens them
after to stream them into the dataflash log. When loading multiple large
Lua scripts from ROMFS, decompression takes a significant amount of
time. This creates the opportunity for the Lua interpreter and logging
threads to both be inside `AP_Filesystem_ROMFS::open()` decompressing a
file.

If this happens, the function can return the same `fd` for two different
calls as the `fd` is chosen before decompression starts, but only marked
as being used after that finishes. The read pointers then stomp on each
other, so Lua loads garbled scripts (usually resulting in a syntax
error) and the logger dumps garbled data.

Fix the issue by locking before searching for a free record (or marking
a record as free). Apply the same fix to directories as well. This
doesn't protect against using the same `fd`/`dirp` from multiple
threads, but that behavior is to be discouraged anyway and is not the
root cause here.
2025-02-13 19:34:53 +09:00

274 lines
6.5 KiB
C++

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
ArduPilot filesystem interface for ROMFS
*/
#include "AP_Filesystem_config.h"
#if AP_FILESYSTEM_ROMFS_ENABLED
#include "AP_Filesystem.h"
#include "AP_Filesystem_ROMFS.h"
#include <AP_HAL/AP_HAL.h>
#include <AP_Math/AP_Math.h>
#include <AP_ROMFS/AP_ROMFS.h>
int AP_Filesystem_ROMFS::open(const char *fname, int flags, bool allow_absolute_paths)
{
if ((flags & O_ACCMODE) != O_RDONLY) {
errno = EROFS;
return -1;
}
WITH_SEMAPHORE(record_sem); // search for free file record
uint8_t idx;
for (idx=0; idx<max_open_file; idx++) {
if (file[idx].data == nullptr) {
break;
}
}
if (idx == max_open_file) {
errno = ENFILE;
return -1;
}
file[idx].data = AP_ROMFS::find_decompress(fname, file[idx].size);
if (file[idx].data == nullptr) {
errno = ENOENT;
return -1;
}
file[idx].ofs = 0;
return idx;
}
int AP_Filesystem_ROMFS::close(int fd)
{
if (fd < 0 || fd >= max_open_file || file[fd].data == nullptr) {
errno = EBADF;
return -1;
}
WITH_SEMAPHORE(record_sem); // release file record
AP_ROMFS::free(file[fd].data);
file[fd].data = nullptr;
return 0;
}
int32_t AP_Filesystem_ROMFS::read(int fd, void *buf, uint32_t count)
{
if (fd < 0 || fd >= max_open_file || file[fd].data == nullptr) {
errno = EBADF;
return -1;
}
count = MIN(file[fd].size - file[fd].ofs, count);
if (count == 0) {
return 0;
}
memcpy(buf, &file[fd].data[file[fd].ofs], count);
file[fd].ofs += count;
return count;
}
int32_t AP_Filesystem_ROMFS::write(int fd, const void *buf, uint32_t count)
{
errno = EROFS;
return -1;
}
int AP_Filesystem_ROMFS::fsync(int fd)
{
return 0;
}
int32_t AP_Filesystem_ROMFS::lseek(int fd, int32_t offset, int seek_from)
{
if (fd < 0 || fd >= max_open_file || file[fd].data == nullptr) {
errno = EBADF;
return -1;
}
switch (seek_from) {
case SEEK_SET:
if (offset < 0) {
errno = EINVAL;
return -1;
}
file[fd].ofs = MIN(file[fd].size, (uint32_t)offset);
break;
case SEEK_CUR:
file[fd].ofs = MIN(file[fd].size, offset+file[fd].ofs);
break;
case SEEK_END:
file[fd].ofs = file[fd].size;
break;
}
return file[fd].ofs;
}
int AP_Filesystem_ROMFS::stat(const char *name, struct stat *stbuf)
{
uint32_t size;
if (!AP_ROMFS::find_size(name, size)) {
errno = ENOENT;
return -1;
}
memset(stbuf, 0, sizeof(*stbuf));
stbuf->st_size = size;
return 0;
}
int AP_Filesystem_ROMFS::unlink(const char *pathname)
{
errno = EROFS;
return -1;
}
int AP_Filesystem_ROMFS::mkdir(const char *pathname)
{
errno = EROFS;
return -1;
}
void *AP_Filesystem_ROMFS::opendir(const char *pathname)
{
WITH_SEMAPHORE(record_sem); // search for free directory record
uint8_t idx;
for (idx=0; idx<max_open_dir; idx++) {
if (dir[idx].path == nullptr) {
break;
}
}
if (idx == max_open_dir) {
errno = ENFILE;
return nullptr;
}
dir[idx].ofs = 0;
dir[idx].path = strdup(pathname);
if (!dir[idx].path) {
return nullptr;
}
// Take a sneak peek and reset
const char *name = AP_ROMFS::dir_list(dir[idx].path, dir[idx].ofs);
dir[idx].ofs = 0;
if (!name) {
// Directory does not exist
return nullptr;
}
return (void*)&dir[idx];
}
struct dirent *AP_Filesystem_ROMFS::readdir(void *dirp)
{
uint32_t idx = ((rdir*)dirp) - &dir[0];
if (idx >= max_open_dir) {
errno = EBADF;
return nullptr;
}
const char *name = AP_ROMFS::dir_list(dir[idx].path, dir[idx].ofs);
if (!name) {
return nullptr;
}
const uint32_t plen = strlen(dir[idx].path);
if (plen > 0) {
// Offset to get just file/directory name
name += plen + 1;
}
// Copy full name
strncpy(dir[idx].de.d_name, name, sizeof(dir[idx].de.d_name));
const char* slash = strchr(name, '/');
if (slash == nullptr) {
// File
#if AP_FILESYSTEM_HAVE_DIRENT_DTYPE
dir[idx].de.d_type = DT_REG;
#endif
} else {
// Directory
#if AP_FILESYSTEM_HAVE_DIRENT_DTYPE
dir[idx].de.d_type = DT_DIR;
#endif
// Add null termination after directory name
const size_t index = slash - name;
dir[idx].de.d_name[index] = 0;
}
return &dir[idx].de;
}
int AP_Filesystem_ROMFS::closedir(void *dirp)
{
uint32_t idx = ((rdir *)dirp) - &dir[0];
if (idx >= max_open_dir) {
errno = EBADF;
return -1;
}
WITH_SEMAPHORE(record_sem); // release directory record
free(dir[idx].path);
dir[idx].path = nullptr;
return 0;
}
// return free disk space in bytes
int64_t AP_Filesystem_ROMFS::disk_free(const char *path)
{
return 0;
}
// return total disk space in bytes
int64_t AP_Filesystem_ROMFS::disk_space(const char *path)
{
return 0;
}
/*
set mtime on a file
*/
bool AP_Filesystem_ROMFS::set_mtime(const char *filename, const uint32_t mtime_sec)
{
return false;
}
/*
Load a file's contents into memory. Returned object must be `delete`d to free
the data. The data is guaranteed to be null-terminated such that it can be
treated as a string. Overridden in ROMFS to avoid taking twice the memory.
*/
FileData *AP_Filesystem_ROMFS::load_file(const char *filename)
{
FileData *fd = NEW_NOTHROW FileData(this);
if (!fd) {
return nullptr;
}
// AP_ROMFS adds the guaranteed termination so we don't have to.
fd->data = AP_ROMFS::find_decompress(filename, fd->length);
if (fd->data == nullptr) {
delete fd;
return nullptr;
}
return fd;
}
// unload data from load_file()
void AP_Filesystem_ROMFS::unload_file(FileData *fd)
{
AP_ROMFS::free(fd->data);
}
#endif // AP_FILESYSTEM_ROMFS_ENABLED