- Removed fennec::path, see #Security Ramblings in PLANNING.md

This commit is contained in:
2025-07-08 23:35:37 -04:00
parent 649e39c70e
commit cc20af7504
8 changed files with 165 additions and 160 deletions

View File

@@ -6,6 +6,7 @@
1. [Introduction](#introduction)
2. [TODO](#todo)
1. [Security Ramblings](#security-ramblings)
3. [C++ Language](#c-language-library-lang)
4. [Math Library](#math-library-math)
5. [Memory Library](#memory-library-memory)
@@ -62,6 +63,41 @@ This however can be achieved using events at different stages of those engines t
- 2D Physics (`physics2d`)
- 2D & 3D Audio (`audio`)
### Security Ramblings:
Windows is starting to piss me off, so I am considering dropping official support for MSVC. MinGW and Cygwin
will still work for compiling on Windows if this ends up being the case. The reason for this is that there are
*a lot* of platform dependent security issues. MinGW and Cygwin wrap Linux and glibc headers for Windows, which would
push the security onus onto the compiler and end-user.
The biggest blocker at the moment in terms of this is the filesystem. If we want to implement a filesystem that
is safe across platforms, stdc++ *and* iso libc have no guarantees about the safety of their functions.
The crux of this issue falls at the following specific behaviour:
- User selects an existing file to write to
- Application interface confirms overwrite action
- Application writes to the file after confirmation
A threat actor can introduce a malicious file or symlink to the file that was attempted access between the check and
usage of the file. This is called TOCTOU (time of check, time of use).
This issue can be solved using `fopen("<file>", "w+")`, however this specific behaviour is not intuitive to those first
learning how to work with file systems. We can attempt to abstract this away with another wrapper, or simply write
the file structure to handle this behaviour properly. The downside to this method overall is that it will break
common conventions of how humans interpret filesystems and the related control flow logic. What we can do is force the
`'+'` flag to always be present for write operations, and raise an error, when desired, if the file is not empty. This
unfortunately would have the downside of being unable to open a file as write only.
Using `"wx"` in this instance would not be sufficient since it would require a second call to fopen, which would
create the conditions for the TOCTOU error described above.
Another issue arises when we are parsing a directory tree. The best we can do is take ownership of the directory that
is opened as the root. However, this requires `dirent.h` which is not implemented in MSVC. A custom implementation of
`dirent.h` may be written for MSVC, however this is one of the few things I am not willing to outsource to another
library. Developing our own implementation would take a non-insignificant amount of time, between writing the library,
debugging it, and testing for vulnerabilities. As stated above, this implementation is native to MinGW and Cygwin,
so we would not have to entirely drop support for Windows. However, MSVC is the most widely used compiler for Windows
applications and is native to Visual Studio and VSCode.

BIN
conv/GNUCodingStandards.pdf Normal file

Binary file not shown.

View File

@@ -1,113 +0,0 @@
// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 Medusa Slockbower
//
// 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 <https://www.gnu.org/licenses/>.
// =====================================================================================================================
#ifndef FENNEC_FPROC_FILESYSTEM_PATH_H
#define FENNEC_FPROC_FILESYSTEM_PATH_H
#include <fennec/fproc/strings/string.h>
namespace fennec
{
struct path
{
public:
using string_t = fennec::string;
///
/// \brief Helper function for getting the file separator for the current system
/// \returns A character containing the file separator
static constexpr char preferred_separator() {
#if defined(_WIN32) || defined(_WIN64)
return '\\';
#else
return '/';
#endif
}
///
/// \brief Default Constructor, initializes empty path
constexpr path()
: _str(), _file(0), _parent(0) {
}
///
/// \brief
/// \param str
constexpr path(const cstring& str)
: _str(str) {
_find_paths();
}
constexpr path(const string& str)
: _str(str) {
_find_paths();
}
constexpr path(string&& str)
: _str(fennec::forward<string_t>(str)) {
_find_paths();
}
constexpr path(const path& path)
: _str(path._str) {
_find_paths();
}
constexpr path(path&& path) noexcept
: _str(fennec::move(path._str)) {
_find_paths();
}
constexpr const string_t& string() const {
return _str;
}
constexpr path parent() const {
return _str.substring(0, _file);
}
constexpr path operator/(const cstring& file) const {
path res = _str + file;
return res;
}
constexpr path operator/(const string_t& file) const {
path res = _str + file;
return res;
}
constexpr path operator/(const path& file) const {
path res = _str + file._str;
return res;
}
private:
constexpr void _find_paths() {
_file = _str.rfind(preferred_separator());
_parent = _str.rfind(preferred_separator(), _file - 1);
}
string_t _str;
size_t _file
, _parent;
};
}
#endif // FENNEC_FPROC_FILESYSTEM_PATH_H

View File

@@ -21,28 +21,45 @@
#include <fennec/fproc/strings/cstring.h>
#include <fennec/fproc/strings/string.h>
#include <fennec/fproc/filesystem/path.h>
struct FILE;
namespace fennec
{
///
/// \brief Mode flags for opening a file
enum fmode : uint8_t
{
read = 0b00000001
, write = 0b00000010
, append = 0b00000100
, no_overwrite = 0b00001000
};
class file
{
public:
file();
~file();
file(const cstring& filename, const cstring& mode);
file(const string& filename, const cstring& mode);
///
/// \brief Opens a file at the provided path using the provided mode
/// \param path the path to the provided file
/// \param mode the flags for opening the file
file(const cstring& path, fmode mode);
file(const string& path, fmode mode);
file(const path& path, fmode mode);
file(const file&) = delete;
// File Access =========================================================================================================
void open(const cstring& filename, const cstring& mode);
void open(const string& filename, const cstring& mode);
void open(const cstring& filename, fmode mode);
void open(const string& filename, fmode mode);
void open(const path& filename, fmode mode);
void close();
void commit();

View File

@@ -54,6 +54,7 @@ using ::toupper;
struct cstring
{
public:
static constexpr size_t npos = -1;
// Constructors ========================================================================================================
@@ -196,21 +197,31 @@ public:
///
/// \returns The length of the string to the first null-terminator
constexpr size_t length() const
{ return find('\0'); }
constexpr size_t length() const {
return find('\0');
}
///
/// \brief String Comparison
/// \param ostr the string to compare against
/// \param str the string to compare against
/// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the
/// current locale, otherwise a positive value.
constexpr int compare(const cstring& str, size_t i = 0) const {
if (i >= _size) return -1;
return ::strcoll(_cstr + i, str);
constexpr int compare(const cstring& str, size_t i = 0, size_t n = npos) const {
if (i >= _size) {
return -1;
}
n = min(n, max(_size, str._size) + 1);
return ::strncmp(_cstr + i, str, n);
}
constexpr bool operator==(const cstring& str) const
{ return compare(str) == 0; }
///
/// \brief String Equality
/// \param str the string to compare against
/// \returns True if all characters are equal, false otherwise
constexpr bool operator==(const cstring& str) const {
return compare(str) == 0;
}
///
/// \brief Finds the index of the first occurrence of `c` in the string
@@ -218,7 +229,10 @@ public:
/// \param i the index to start at
/// \returns The index of `c` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(char c, size_t i = 0) const {
if (i >= _size) return _size; // bounds check
if (i >= _size) { // bounds check
return _size;
}
const char* loc = ::strchr(_cstr + i, c); // get location using strchr
return loc ? loc - _cstr : _size; // return size if not found
}
@@ -229,7 +243,10 @@ public:
/// \param i the index to start at
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(const cstring& str, size_t i = 0) const {
if (i + str._size > _size) return _size; // bounds check
if (i + str._size > _size) { // bounds check
return _size;
}
const char* loc = ::strstr(_cstr + i, str); // get location using strstr
return loc ? loc - _cstr : _size; // return size if not found
}
@@ -239,8 +256,11 @@ public:
/// \param c the string to find
/// \param i the index to start at
/// \returns The index of `c` if it occurs in the string, otherwise returns `size()`
constexpr size_t rfind(char c, size_t i = 0) const {
if (_size == 0) return _size;
constexpr size_t rfind(char c, size_t i = npos) const {
if (_size == 0) {
return _size;
}
i = min(i, _size - 1); // clamp i to bounds
do {
if (_cstr[i] == c) return i; // loop backwards looking for c
@@ -253,12 +273,19 @@ public:
/// \param str the string to find
/// \param i the index to start at
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t rfind(const cstring& str, size_t i = 0) const {
constexpr size_t rfind(const cstring& str, size_t i = npos) const {
if (_size == 0) {
return _size;
}
const char first = str[0];
i = min(i, _size - str._size);
do {
if(_cstr[i] == first)
if (compare(str, i) == 0) return i; // loop backwards looking for str
if(_cstr[i] == first) {
if (compare(str, i, str._size - 1) == 0) {
return i;
}
} // loop backwards looking for str
} while (i--);
return _size; // base case
}

View File

@@ -19,13 +19,16 @@
#ifndef FENNEC_FPROC_STRINGS_STRING_H
#define FENNEC_FPROC_STRINGS_STRING_H
#include <fennec/fproc/strings/cstring.h>
#include <fennec/fproc/strings/detail/__ctype.h>
#include <fennec/fproc/strings/cstring.h>
#include <fennec/lang/assert.h>
#include <fennec/memory/allocator.h>
#include <fennec/memory/common.h>
#include <fennec/math/common.h>
namespace fennec
{
@@ -43,6 +46,7 @@ template<typename AllocT = allocator<char>>
struct _string
{
public:
static constexpr size_t npos = -1;
using alloc_t = allocation<char, AllocT>;
@@ -139,7 +143,8 @@ public:
///
/// \brief Implicit Dereference Cast
constexpr operator const char*() const {
return _str; }
return _str;
}
// Examination =========================================================================================================
@@ -155,24 +160,37 @@ public:
/// \param ostr the string to compare against
/// \returns Zero if both strings are equal, otherwise a negative value if lhs appears before rhs according to the
/// current locale, otherwise a positive value.
constexpr int compare(const string& ostr) const {
return ::strcoll(_str, ostr);
constexpr int compare(const cstring& str, size_t i = 0, size_t n = npos) const {
if (i >= size()) { // bounds check
return -1;
}
n = min(n, max(_str, str.size()) + 1);
return ::strncmp(_str + i, str, n);
}
///
/// \brief Finds the index of the first occurrence of `x` in the string
/// \param x the character to find
/// \returns The index of `x` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(char x) const {
const char* loc = ::strchr(_str, x);
return loc ? loc - _str : size();
constexpr size_t find(char c, size_t i = 0) const {
if (i >= size()) { // bounds check
return size();
}
const char* loc = ::strchr(_str + i, c); // get location using strchr
return loc ? loc - _str : size(); // return size if not found
}
///
/// \brief Finds the index of the first occurrence of `str` in the string.
/// \param str the string to find
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(const string& str) const {
constexpr size_t find(const string& str, size_t i = 0) const { // bounds check
if (i >= size()) { // bounds check
return size();
}
const char* loc = ::strstr(_str, str);
return loc ? loc - _str : size();
}
@@ -181,9 +199,13 @@ public:
/// \brief Finds the index of the first occurrence of `str` in the string.
/// \param str the string to find
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t find(const cstring& str) const {
const char* loc = ::strstr(_str, str);
return loc ? loc - _str : size();
constexpr size_t find(const cstring& str, size_t i = 0) const {
if (i + str.size() > size()) { // bounds check
return size();
}
const char* loc = ::strstr(_str + i, str); // get location using strstr
return loc ? loc - _str : size(); // return size if not found
}
///
@@ -191,11 +213,15 @@ public:
/// \param c the string to find
/// \param i the index to start at
/// \returns The index of `c` if it occurs in the string, otherwise returns `size()`
constexpr size_t rfind(char c, size_t i = size()) const {
if (size() == 0) return size();
constexpr size_t rfind(char c, size_t i = npos) const {
if (size() == 0) {
return size();
}
i = min(i, size() - 1); // clamp i to bounds
do {
if (_str[i] == c) return i; // loop backwards looking for c
if (_str[i] == c) { // loop backwards looking for c
return i;
}
} while (i--);
return size(); // base case
}
@@ -205,12 +231,18 @@ public:
/// \param str the string to find
/// \param i the index to start at
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t rfind(const cstring& str, size_t i = size()) const {
constexpr size_t rfind(const cstring& str, size_t i = npos) const {
if (size() == 0) {
return size();
}
const char first = str[0];
i = min(i, size() - str.size());
do {
if(_str[i] == first)
if (compare(str, i) == 0) return i; // loop backwards looking for str
if(_str[i] == first) {
if (compare(str, i) == 0) {
return i;
}
}
} while (i--);
return size(); // base case
}
@@ -220,12 +252,18 @@ public:
/// \param str the string to find
/// \param i the index to start at
/// \returns The index of `str` if it occurs in the string, otherwise returns `size()`
constexpr size_t rfind(const string& str, size_t i = size()) const {
constexpr size_t rfind(const string& str, size_t i = npos) const {
if (size() == 0) {
return size();
}
const char first = str[0];
i = min(i, size() - str.size());
do {
if(_str[i] == first)
if (compare(str, i) == 0) return i; // loop backwards looking for str
if(_str[i] == first) {
if (compare(str, i) == 0) { // loop backwards looking for str
return i;
}
}
} while (i--);
return size(); // base case
}
@@ -313,7 +351,7 @@ public:
constexpr string& operator+=(const string& str) {
size_t x = size();
string::resize(x + str.size());
resize(x + str.size());
fennec::memcpy(&_str[x], str, str.size());
return *this;
}

View File

@@ -324,8 +324,7 @@ struct vector : detail::vector_base_type<ScalarT, sizeof...(IndicesV)>
/// \tparam SwizzleIndicesV swizzle Indices
/// \param swizzle swizzle object
template<typename SwizzleDataT, typename SwizzleScalarT, size_t... SwizzleIndicesV>
explicit constexpr vector(
const detail::swizzle_storage<SwizzleDataT, SwizzleScalarT, SwizzleIndicesV...>& swizzle) {
explicit constexpr vector(const detail::swizzle_storage<SwizzleDataT, SwizzleScalarT, SwizzleIndicesV...>& swizzle) {
((data[IndicesV] = ScalarT(swizzle[IndicesV])), ...);
}

View File

@@ -40,11 +40,12 @@ inline void fennec_test_fproc_strings_cstring()
fennec_test_spacer(1);
fennec_test_run(str.length(), size_t(12));
fennec_test_run(str.compare(cstr), 0);
fennec_test_run(str.find('W'), size_t(6));
fennec_test_run(str.find("World"), size_t(6));
fennec_test_run(str.rfind('o'), size_t(7));
fennec_test_run(str.length(), size_t(12));
fennec_test_run(str.compare(cstr), 0);
fennec_test_run(str.find('W'), size_t(6));
fennec_test_run(str.find("World"), size_t(6));
fennec_test_run(str.rfind('o'), size_t(7));
fennec_test_run(str.rfind("World"), size_t(6));
fennec_test_spacer(2);