/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/util/utils.cpp
180 строк
7 KB
Андрей Смычков
security: комплексные фиксы по результатам аудита
25 апр 2026, 10:00
25 апр 2026, 10:00
5f1be8e
Код
Авторство
О чём код?
#include "utils.hpp" #include <algorithm> #include <cerrno> #include <chrono> #include <climits> #include <cstdlib> #include <cstring> #include <ctime> #include <iomanip> #include <sstream> #include <sys/stat.h> namespace sstorage { namespace util { //============================================================================ // Текущий timestamp для логов //============================================================================ std::string currentTimestampHuman() { auto now = std::chrono::system_clock::now(); auto t = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()) % 1000; std::tm tm; #ifdef _WIN32 localtime_s(&tm, &t); #else localtime_r(&t, &tm); #endif std::ostringstream oss; oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3) << ms.count(); return oss.str(); } //============================================================================ // Время в наносекундах //============================================================================ uint64_t getCurrentTimeNanos() { return std::chrono::system_clock::now().time_since_epoch().count(); } //============================================================================ // Файловые утилиты //============================================================================ bool fileExists(const std::string& path) { struct stat st; return ::stat(path.c_str(), &st) == 0; } bool createDirectory(const std::string& path) { return ::mkdir(path.c_str(), 0755) == 0 || errno == EEXIST; } //============================================================================ // Строковые утилиты //============================================================================ std::string trim(const std::string& str) { auto start = std::find_if(str.begin(), str.end(), [](unsigned char ch) { return !std::isspace(ch); }); auto end = std::find_if(str.rbegin(), str.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(); return (start < end) ? std::string(start, end) : std::string(); } std::vector<std::string> split(const std::string& str, char delimiter) { std::vector<std::string> tokens; std::string token; std::istringstream iss(str); while (std::getline(iss, token, delimiter)) { tokens.push_back(token); } return tokens; } //============================================================================ // isPathSafe — защита от Path Traversal и command injection //============================================================================ bool isPathSafe(const std::string& path) { if (path.empty()) return false; if (path.find("..") != std::string::npos) return false; if (path.find('/') != std::string::npos) return false; if (path.find('\0') != std::string::npos) return false; const std::string unsafe = "|;&`$<>\"'{}[]()!#*?"; for (char c : path) { if (unsafe.find(c) != std::string::npos) return false; } return true; } //============================================================================ // validateDataDirectory — валидация директории данных //============================================================================ // Защищает от двух сценариев: // 1. Path traversal: --data-dir "../../etc" → сервер пишет в /etc. // 2. Запись в системные пути: --data-dir "/etc" → порча системы. // // Логика: // - NULL-байт или управляющие символы в пути → reject. // - Если путь начин��ется с "/" (абсолютный) или "." (относительный) — ок, // остальное (например, "some/relative") преобразуем к "./some/relative". // - Resolved путь через realpath() не должен начинаться с известных // системных префиксов. // - Пустой путь и суррогатные форматы отвергаем. //============================================================================ bool validateDataDirectory(const std::string& path, std::string& resolved) { resolved.clear(); if (path.empty()) return false; // Управляющие символы и NULL for (char c : path) { if (c == '\0') return false; if (static_cast<unsigned char>(c) < 0x20) return false; } // Нормализуем: если не абсолютный и не начинается с '.', добавляем "./" std::string normalized = path; if (normalized[0] != '/' && normalized[0] != '.') { normalized = "./" + normalized; } // Сначала проверяем пользовательский ввод на наличие системных префиксов // ДО realpath (который может быть недоступен для несуществующих путей // или добавить префикс вроде /private). Проверяем оба случая: // и как ввёл пользователь, и после канонизации. static const char* kBlocked[] = { "/etc", "/proc", "/sys", "/dev", "/boot", "/bin", "/sbin", "/usr/bin", "/usr/sbin", "/usr/lib", "/lib", "/lib64", // macOS: /etc → /private/etc, /var → /private/var "/private/etc", "/private/var/db", "/System" }; auto isBlocked = [](const std::string& p) -> bool { for (const char* prefix : kBlocked) { size_t plen = std::strlen(prefix); if (p.size() >= plen && p.compare(0, plen, prefix) == 0 && (p.size() == plen || p[plen] == '/')) { return true; } } return false; }; // Проверка 1: исходный путь (до realpath) if (isBlocked(normalized)) return false; // Пытаемся получить канонический путь. Если директории ещё нет — // резолвим родителя и приклеиваем имя последнего компонента. char buf[PATH_MAX]; std::string canonical; if (::realpath(normalized.c_str(), buf) != nullptr) { canonical = buf; } else { size_t slash = normalized.find_last_of('/'); if (slash == std::string::npos) return false; std::string parent = slash == 0 ? std::string("/") : normalized.substr(0, slash); std::string leaf = normalized.substr(slash + 1); if (leaf.empty()) return false; if (::realpath(parent.c_str(), buf) == nullptr) { // Родитель не существует — проверяем хотя бы по prefix исходного canonical = normalized; } else { canonical = std::string(buf) + "/" + leaf; } } // Проверка 2: канонический путь (после realpath, может отличаться) if (isBlocked(canonical)) return false; resolved = std::move(canonical); return true; } } }