storage: Don't allow "." and ".." paths, enforce '/' dir separators.

Also clarify what characters are valid for Storage paths in the category docs.

Fixes #11079.
Fixes #11370.
Fixes #11369.
This commit is contained in:
Ryan C. Gordon
2025-01-15 20:16:10 -05:00
parent 67664a0427
commit 874c07f8de
4 changed files with 162 additions and 40 deletions

View File

@@ -56,6 +56,33 @@ struct SDL_Storage
return result; \
}
// we don't make any effort to convert path separators here, because a)
// everything including Windows will accept a '/' separator and b) that
// conversion should probably happen in the storage backend anyhow.
static bool ValidateStoragePath(const char *path)
{
if (SDL_strchr(path, '\\')) {
return SDL_SetError("Windows-style path separators ('\\') not permitted, use '/' instead.");
}
const char *ptr;
const char *prev = path;
while ((ptr = SDL_strchr(prev, '/')) != NULL) {
if ((SDL_strncmp(prev, "./", 2) == 0) || (SDL_strncmp(prev, "../", 3) == 0)) {
return SDL_SetError("Relative paths not permitted");
}
prev = ptr + 1;
}
// check the last path element (or the only path element).
if ((SDL_strcmp(prev, ".") == 0) || (SDL_strcmp(prev, "..") == 0)) {
return SDL_SetError("Relative paths not permitted");
}
return true;
}
SDL_Storage *SDL_OpenTitleStorage(const char *override, SDL_PropertiesID props)
{
SDL_Storage *storage = NULL;
@@ -213,9 +240,9 @@ bool SDL_ReadStorageFile(SDL_Storage *storage, const char *path, void *destinati
if (!path) {
return SDL_InvalidParamError("path");
}
if (!storage->iface.read_file) {
} else if (!ValidateStoragePath(path)) {
return false;
} else if (!storage->iface.read_file) {
return SDL_Unsupported();
}
@@ -228,9 +255,9 @@ bool SDL_WriteStorageFile(SDL_Storage *storage, const char *path, const void *so
if (!path) {
return SDL_InvalidParamError("path");
}
if (!storage->iface.write_file) {
} else if (!ValidateStoragePath(path)) {
return false;
} else if (!storage->iface.write_file) {
return SDL_Unsupported();
}
@@ -243,9 +270,9 @@ bool SDL_CreateStorageDirectory(SDL_Storage *storage, const char *path)
if (!path) {
return SDL_InvalidParamError("path");
}
if (!storage->iface.mkdir) {
} else if (!ValidateStoragePath(path)) {
return false;
} else if (!storage->iface.mkdir) {
return SDL_Unsupported();
}
@@ -258,9 +285,9 @@ bool SDL_EnumerateStorageDirectory(SDL_Storage *storage, const char *path, SDL_E
if (!path) {
return SDL_InvalidParamError("path");
}
if (!storage->iface.enumerate) {
} else if (!ValidateStoragePath(path)) {
return false;
} else if (!storage->iface.enumerate) {
return SDL_Unsupported();
}
@@ -273,9 +300,9 @@ bool SDL_RemoveStoragePath(SDL_Storage *storage, const char *path)
if (!path) {
return SDL_InvalidParamError("path");
}
if (!storage->iface.remove) {
} else if (!ValidateStoragePath(path)) {
return false;
} else if (!storage->iface.remove) {
return SDL_Unsupported();
}
@@ -288,12 +315,13 @@ bool SDL_RenameStoragePath(SDL_Storage *storage, const char *oldpath, const char
if (!oldpath) {
return SDL_InvalidParamError("oldpath");
}
if (!newpath) {
} else if (!newpath) {
return SDL_InvalidParamError("newpath");
}
if (!storage->iface.rename) {
} else if (!ValidateStoragePath(oldpath)) {
return false;
} else if (!ValidateStoragePath(newpath)) {
return false;
} else if (!storage->iface.rename) {
return SDL_Unsupported();
}
@@ -306,12 +334,13 @@ bool SDL_CopyStorageFile(SDL_Storage *storage, const char *oldpath, const char *
if (!oldpath) {
return SDL_InvalidParamError("oldpath");
}
if (!newpath) {
} else if (!newpath) {
return SDL_InvalidParamError("newpath");
}
if (!storage->iface.copy) {
} else if (!ValidateStoragePath(oldpath)) {
return false;
} else if (!ValidateStoragePath(newpath)) {
return false;
} else if (!storage->iface.copy) {
return SDL_Unsupported();
}
@@ -331,9 +360,9 @@ bool SDL_GetStoragePathInfo(SDL_Storage *storage, const char *path, SDL_PathInfo
if (!path) {
return SDL_InvalidParamError("path");
}
if (!storage->iface.info) {
} else if (!ValidateStoragePath(path)) {
return false;
} else if (!storage->iface.info) {
return SDL_Unsupported();
}
@@ -365,6 +394,14 @@ static bool GlobStorageDirectoryEnumerator(const char *path, SDL_EnumerateDirect
char **SDL_GlobStorageDirectory(SDL_Storage *storage, const char *path, const char *pattern, SDL_GlobFlags flags, int *count)
{
CHECK_STORAGE_MAGIC_RET(NULL)
if (!path) {
SDL_InvalidParamError("path");
return NULL;
} else if (!ValidateStoragePath(path)) {
return NULL;
}
return SDL_InternalGlobDirectory(path, pattern, flags, count, GlobStorageDirectoryEnumerator, GlobStorageDirectoryGetPathInfo, storage);
}

View File

@@ -26,15 +26,8 @@
static char *GENERIC_INTERNAL_CreateFullPath(const char *base, const char *relative)
{
if (!base) {
return SDL_strdup(relative);
}
size_t len = SDL_strlen(base) + SDL_strlen(relative) + 1;
char *result = (char*)SDL_malloc(len);
if (result != NULL) {
SDL_snprintf(result, len, "%s%s", base, relative);
}
char *result = NULL;
SDL_asprintf(&result, "%s%s", base ? base : "", relative);
return result;
}
@@ -56,8 +49,27 @@ static SDL_EnumerationResult SDLCALL GENERIC_EnumerateDirectory(void *userdata,
// SDL_EnumerateDirectory will return the full path, so for Storage we
// can take the base directory and add its length to the dirname string,
// effectively trimming the root without having to strdup anything.
GenericEnumerateData *wrap_data = (GenericEnumerateData *)userdata;
return wrap_data->real_callback(wrap_data->real_userdata, dirname + wrap_data->base_len, fname);
const GenericEnumerateData *wrap_data = (GenericEnumerateData *)userdata;
dirname += wrap_data->base_len; // skip the base, just return the part inside of the Storage.
#ifdef SDL_PLATFORM_WINDOWS
char *dirnamecpy = NULL;
const size_t slen = SDL_strlen(dirname);
if (slen && (dirname[slen - 1] == '\\')) {
dirnamecpy = SDL_strdup(dirname);
if (!dirnamecpy) {
return SDL_ENUM_FAILURE;
}
dirnamecpy[slen - 1] = '/'; // storage layer always uses '/' path separators.
dirname = dirnamecpy;
}
const SDL_EnumerationResult retval = wrap_data->real_callback(wrap_data->real_userdata, dirname, fname);
SDL_free(dirnamecpy);
return retval;
#else
return wrap_data->real_callback(wrap_data->real_userdata, dirname, fname);
#endif
}
static bool GENERIC_EnumerateStorageDirectory(void *userdata, const char *path, SDL_EnumerateDirectoryCallback callback, void *callback_userdata)