diff --git a/PLANNING.md b/PLANNING.md index 352713f..5160077 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -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("", "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. diff --git a/conv/GNUCodingStandards.pdf b/conv/GNUCodingStandards.pdf new file mode 100644 index 0000000..10cc8ad Binary files /dev/null and b/conv/GNUCodingStandards.pdf differ diff --git a/include/fennec/fproc/filesystem/path.h b/include/fennec/fproc/filesystem/path.h deleted file mode 100644 index b55a368..0000000 --- a/include/fennec/fproc/filesystem/path.h +++ /dev/null @@ -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 . -// ===================================================================================================================== - -#ifndef FENNEC_FPROC_FILESYSTEM_PATH_H -#define FENNEC_FPROC_FILESYSTEM_PATH_H -#include - -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(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 diff --git a/include/fennec/fproc/io/file.h b/include/fennec/fproc/io/file.h index d450175..a8e6cf9 100644 --- a/include/fennec/fproc/io/file.h +++ b/include/fennec/fproc/io/file.h @@ -21,28 +21,45 @@ #include #include +#include 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(); diff --git a/include/fennec/fproc/strings/cstring.h b/include/fennec/fproc/strings/cstring.h index b113eb4..44c8233 100644 --- a/include/fennec/fproc/strings/cstring.h +++ b/include/fennec/fproc/strings/cstring.h @@ -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 } diff --git a/include/fennec/fproc/strings/string.h b/include/fennec/fproc/strings/string.h index c8c08b0..70e2eca 100644 --- a/include/fennec/fproc/strings/string.h +++ b/include/fennec/fproc/strings/string.h @@ -19,13 +19,16 @@ #ifndef FENNEC_FPROC_STRINGS_STRING_H #define FENNEC_FPROC_STRINGS_STRING_H -#include #include +#include #include + #include #include +#include + namespace fennec { @@ -43,6 +46,7 @@ template> struct _string { public: + static constexpr size_t npos = -1; using alloc_t = allocation; @@ -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; } diff --git a/include/fennec/math/vector.h b/include/fennec/math/vector.h index 081c258..e3773e0 100644 --- a/include/fennec/math/vector.h +++ b/include/fennec/math/vector.h @@ -324,8 +324,7 @@ struct vector : detail::vector_base_type /// \tparam SwizzleIndicesV swizzle Indices /// \param swizzle swizzle object template - explicit constexpr vector( - const detail::swizzle_storage& swizzle) { + explicit constexpr vector(const detail::swizzle_storage& swizzle) { ((data[IndicesV] = ScalarT(swizzle[IndicesV])), ...); } diff --git a/test/tests/fproc/strings/test_cstring.h b/test/tests/fproc/strings/test_cstring.h index 1e784e9..fd2e08b 100644 --- a/test/tests/fproc/strings/test_cstring.h +++ b/test/tests/fproc/strings/test_cstring.h @@ -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);