* This file is part of the MindStudio project.
* Copyright (c) 2025 Huawei Technologies Co.,Ltd.
*
* MindStudio is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*/
#include "msServiceProfiler/SecurityUtils.h"
#include <iostream>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <climits>
#include <algorithm>
#include <regex>
#include <cstdlib>
#include "msServiceProfiler/SecurityUtilsLog.h"
namespace SecurityUtils {
bool IsExist(const std::string &absPath)
{
struct stat fileStat;
if (stat(absPath.c_str(), &fileStat) != 0) {
LogWarn("File not exist: %s", absPath.c_str());
return false;
}
return true;
}
bool IsReadable(const std::string &absPath)
{
struct stat fileStat;
if ((stat(absPath.c_str(), &fileStat) != 0) || (fileStat.st_mode & S_IRUSR) == 0) {
LogWarn("File not readable: %s", absPath.c_str());
return false;
}
return true;
}
bool IsWritable(const std::string &absPath)
{
struct stat fileStat;
if ((stat(absPath.c_str(), &fileStat) != 0) || (fileStat.st_mode & S_IWUSR) == 0) {
LogWarn("File not writable: %s", absPath.c_str());
return false;
}
return true;
}
bool IsExecutable(const std::string &absPath)
{
struct stat fileStat;
if ((stat(absPath.c_str(), &fileStat) != 0) || (fileStat.st_mode & S_IXUSR) == 0) {
LogWarn("File not executable: %s", absPath.c_str());
return false;
}
return true;
}
bool IsOwner(const std::string &absPath)
{
struct stat fileStat;
if ((stat(absPath.c_str(), &fileStat) != 0) || (fileStat.st_uid != getuid())) {
LogWarn("File not owned by current user: %s", absPath.c_str());
return false;
}
return true;
}
bool IsRootUser()
{
constexpr __uid_t root = 0;
return getuid() == root;
}
bool IsSoftLink(const std::string &absPath)
{
struct stat fileStat;
if ((lstat(absPath.c_str(), &fileStat) != 0) || ((S_IFMT & fileStat.st_mode) != S_IFLNK)) {
return false;
} else {
LogWarn("File is symbol link: %s", absPath.c_str());
return true;
}
}
bool IsFile(const std::string &absPath)
{
struct stat fileStat;
if ((stat(absPath.c_str(), &fileStat) != 0) || !S_ISREG(fileStat.st_mode)) {
LogWarn("Path is not regular file: %s", absPath.c_str());
return false;
}
return true;
}
bool IsDir(std::string const &absPath)
{
struct stat fileStat;
if (stat(absPath.c_str(), &fileStat) != 0 || (fileStat.st_mode & S_IFDIR) == 0) {
LogWarn("Path is not directory: %s", absPath.c_str());
return false;
}
return true;
}
bool IsPathLenLegal(const std::string &absPath)
{
if (absPath.empty() || (absPath.size() >= PATH_MAX)) {
LogWarn("Path length is ilegal");
return false;
}
return true;
}
bool IsPathDepthLegal(const std::string &absPath)
{
if (std::count(absPath.begin(), absPath.end(), PATH_SEPARATOR) > PATH_DEPTH_MAX) {
LogWarn("Path depth exceeds limit");
return false;
}
return true;
}
bool IsFileSizeLegal(const std::string &absPath, uint64_t maxSize)
{
struct stat fileStat;
if (stat(absPath.c_str(), &fileStat) != 0 || !S_ISREG(fileStat.st_mode)) {
LogWarn("File does not exist or not a regular file, path: %s", absPath.c_str());
return false;
}
if (fileStat.st_size < 0) {
LogWarn("File size unexpectedly negative, path: %s", absPath.c_str());
return false;
}
if (static_cast<uint64_t>(fileStat.st_size) > maxSize) {
LogWarn("File too large: %lld > %llu, path: %s", fileStat.st_size, maxSize, absPath.c_str());
return false;
}
return true;
}
bool IsPathCharactersValid(const std::string &absPath)
{
if (!std::regex_match(absPath, std::regex(FILE_VALID_PATTERN))) {
LogWarn("File path contain invalid characters");
return false;
}
return true;
}
static std::string GetRealPath(std::string const &path)
{
char absPath[PATH_MAX + 1] = {0};
if (realpath(path.c_str(), absPath) == nullptr) {
LogWarn("Cannot GetRealPath: %s", path.c_str());
return "";
}
return std::string(absPath);
}
static std::string GetParentDir(const std::string &path)
{
if (path.empty() || path == "/") {
LogWarn("Cannot GetParentDir");
}
size_t found = path.find_last_of('/');
if (found != std::string::npos) {
return path.substr(0, found);
}
return ".";
}
static void Split(std::string const &str, std::back_insert_iterator<std::vector<std::string>> it,
std::string const &seps)
{
if (!seps.empty() && !str.empty() && str.find_first_of(seps) == 0) {
*it = "";
++it;
}
std::string::size_type slow = str.find_first_not_of(seps);
while (slow != std::string::npos && slow < str.length()) {
std::string::size_type fast = str.find_first_of(seps, slow);
if (fast == std::string::npos) {
fast = str.length();
}
if (fast != slow) {
*it = str.substr(slow, fast - slow);
++it;
}
slow = fast + 1;
}
}
bool CheckPathContainSoftLink(const std::string &path)
{
constexpr char const *pathSep = "/";
std::string nonConstPath = path;
while (!nonConstPath.empty() && nonConstPath.back() == '/') {
nonConstPath.pop_back();
}
std::vector<std::string> dirs;
Split(nonConstPath, std::back_inserter(dirs), pathSep);
if (dirs.empty()) {
return false;
}
std::string current;
for (auto it = dirs.cbegin(); it != dirs.cend(); ++it) {
if (it == dirs.cbegin()) {
current = *it;
} else {
current.append(pathSep + *it);
}
if (*it == "." || *it == ".." || *it == "") {
continue;
}
if (SecurityUtils::IsSoftLink(current)) {
return true;
}
}
return false;
}
static mode_t GetFilePermissions(const std::string &path)
{
struct stat fileStat;
if (stat(path.c_str(), &fileStat) != 0) {
return 0;
}
mode_t permissions = fileStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
return permissions;
}
bool CheckFileBeforeWrite(const std::string &path)
{
if (IsSoftLink(path)) {
return false;
}
const auto absPath = GetRealPath(path);
if (!IsPathLenLegal(absPath) || !IsPathCharactersValid(absPath) || !IsFile(absPath) ||
!IsOwner(GetParentDir(absPath)) || !IsOwner(absPath) || !IsWritable(absPath)) {
LogWarn("CheckFileBeforeWrite faild, path: %s", path.c_str());
return false;
}
const mode_t defaultWriteFilePerm = 0640;
if (GetFilePermissions(absPath) > defaultWriteFilePerm) {
LogWarn("Permission of file to write is over 0640, path: %s", path.c_str());
}
return true;
}
bool CheckFileBeforeRead(const std::string &path, long long maxSize)
{
if (IsSoftLink(path)) {
return false;
}
const auto absPath = GetRealPath(path);
if (!IsPathLenLegal(absPath) || !IsPathCharactersValid(absPath) || !IsFile(absPath)) {
LogWarn("CheckFileBeforeRead faild, path: %s", path.c_str());
return false;
}
struct stat fileStat;
if (stat(absPath.c_str(), &fileStat) != 0) {
LogWarn("File not exist: %s", absPath.c_str());
return false;
}
if ((fileStat.st_mode & (S_IWGRP | S_IWOTH)) != 0) {
LogWarn("Group or others user can write, path: %s", path.c_str());
return false;
}
if (fileStat.st_uid != getuid() && fileStat.st_uid != 0) {
LogWarn("file owner is not self or root: %s", path.c_str());
return false;
}
if (!IsReadable(absPath)) {
return false;
}
if (!IsFileSizeLegal(absPath, maxSize)) {
return false;
}
return true;
}
}