* Copyright (c) Huawei Technologies Co., Ltd. 2020-2022. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "utils.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "logger.h"
#include "securec.h"
#define LOG_LENGTH 1024
static bool g_checkWgroup = true;
static bool g_allowLink = false;
char *FormatLogMessage(char *format, ...)
{
if (format == NULL)
{
(void)fprintf(stderr, "format pointer is null!\n");
return NULL;
}
va_list list;
va_start(list, format);
char buff[LOG_LENGTH] = {0};
int ret = vsnprintf_s(buff, sizeof(buff), sizeof(buff) - 1, format, list);
va_end(list);
if (ret <= 0)
{
return NULL;
}
size_t size = (size_t)ret;
size++;
va_start(list, format);
char *buf = (char *)malloc(size);
if (buf == NULL)
{
va_end(list);
return NULL;
}
ret = vsnprintf_s(buf, size, size - 1, format, list);
va_end(list);
if (ret <= 0)
{
free(buf);
buf = NULL;
return NULL;
}
return buf;
}
STATIC int MkDir(const char *dir, mode_t mode)
{
if (dir == NULL)
{
(void)fprintf(stderr, "dir pointer is null!\n");
return -1;
}
return mkdir(dir, mode);
}
int VerifyPathInfo(const struct PathInfo *pathInfo)
{
if (pathInfo == NULL || pathInfo->dst == NULL || pathInfo->src == NULL)
{
return -1;
}
return 0;
}
int CheckDirExists(const char *dir)
{
if (dir == NULL)
{
(void)fprintf(stderr, "dir pointer is null!\n");
return -1;
}
DIR *ptr = opendir(dir);
if (NULL == ptr)
{
return -1;
}
closedir(ptr);
return 0;
}
int GetParentPathStr(const char *path, char *parent, size_t bufSize)
{
if (path == NULL || parent == NULL)
{
(void)fprintf(stderr, "path pointer or parentPath is null!\n");
return -1;
}
char *ptr = strrchr(path, '/');
if (ptr == NULL)
{
return 0;
}
int len = (int)strlen(path) - (int)strlen(ptr);
if (len < 1)
{
return 0;
}
errno_t ret = strncpy_s(parent, bufSize, path, len);
if (ret != EOK)
{
return -1;
}
return 0;
}
int MakeDirWithParent(const char *path, mode_t mode)
{
if (path == NULL)
{
(void)fprintf(stderr, "path pointer is null!\n");
return -1;
}
if (*path == '\0' || *path == '.')
{
return 0;
}
if (CheckDirExists(path) == 0)
{
return 0;
}
char parentPath[BUF_SIZE] = {0};
GetParentPathStr(path, parentPath, BUF_SIZE);
if (strlen(parentPath) > 0 && MakeDirWithParent(parentPath, mode) < 0)
{
return -1;
}
int ret = MkDir(path, mode);
if (ret < 0)
{
return -1;
}
return 0;
}
int MakeMountPoints(const char *path, mode_t mode)
{
if (path == NULL)
{
(void)fprintf(stderr, "path pointer is null!\n");
return -1;
}
char parentDir[BUF_SIZE] = {0};
GetParentPathStr(path, parentDir, BUF_SIZE);
int ret = MakeDirWithParent(parentDir, DEFAULT_DIR_MODE);
if (ret < 0)
{
Logger("Failed to make parent dir for file.", LEVEL_ERROR, SCREEN_YES);
return -1;
}
char resolvedPath[PATH_MAX] = {0};
if (realpath(path, resolvedPath) == NULL && errno != ENOENT)
{
Logger("failed to resolve path.", LEVEL_ERROR, SCREEN_YES);
return -1;
}
int fd = open(resolvedPath, O_NOFOLLOW | O_CREAT, mode);
if (fd < 0)
{
Logger("cannot create file.", LEVEL_ERROR, SCREEN_YES);
return -1;
}
close(fd);
return 0;
}
STATIC bool ShowExceptionInfo(const char *exceptionInfo)
{
(void)fprintf(stderr, "%s\n", exceptionInfo);
return false;
}
STATIC bool CheckFileOwner(const struct stat fileStat, const bool checkOwner)
{
if (checkOwner)
{
if ((fileStat.st_uid != ROOT_UID) && (fileStat.st_uid != geteuid()))
{
return ShowExceptionInfo("Please check the folder owner!");
}
}
return true;
}
STATIC bool CheckParentDir(const char *filePath, const size_t filePathLen, struct stat fileStat, const bool checkOwner)
{
char buf[PATH_MAX] = {0};
if (strncpy_s(buf, sizeof(buf), filePath, filePathLen) != EOK)
{
return false;
}
for (int iLoop = 0; iLoop < PATH_MAX; iLoop++)
{
if (!CheckFileOwner(fileStat, checkOwner))
{
return false;
}
if ((fileStat.st_mode & S_IWOTH) != 0)
{
return ShowExceptionInfo("Please check the write permission!");
}
if (g_checkWgroup && ((fileStat.st_mode & S_IWGRP) != 0))
{
return ShowExceptionInfo("Please check the write permission!");
}
if (S_ISLNK(fileStat.st_mode) != 0)
{
return ShowExceptionInfo("resolvedPath is symbolic link!");
}
if ((strcmp(buf, "/") == 0) || (strstr(buf, "/") == NULL))
{
break;
}
if (strcmp(dirname(buf), ".") == 0)
{
break;
}
if (lstat(buf, &fileStat) != 0)
{
return false;
}
}
return true;
}
STATIC bool CheckLegality(const char *filePath, const size_t filePathLen, const unsigned long long maxFileSizeMb,
const bool checkOwner)
{
const unsigned long long maxFileSizeB = maxFileSizeMb * 1024 * 1024;
char buf[PATH_MAX] = {0};
if (strncpy_s(buf, sizeof(buf), filePath, filePathLen) != EOK)
{
return false;
}
struct stat fileStat;
if ((lstat(buf, &fileStat) != 0) || (!g_allowLink && (S_ISLNK(fileStat.st_mode) != 0)) ||
((S_ISREG(fileStat.st_mode) == 0) && (S_ISDIR(fileStat.st_mode) == 0)))
{
return ShowExceptionInfo("filePath does not exist or is not a file/dir!");
}
if ((maxFileSizeMb > 0) && (fileStat.st_size >= maxFileSizeB))
{
return ShowExceptionInfo("fileSize out of bounds!");
}
return CheckParentDir(filePath, filePathLen, fileStat, checkOwner);
}
bool IsValidChar(const char c)
{
if (isalnum(c) != 0)
{
return true;
}
if ((c == '.') || (c == '_') || (c == '-') || (c == '/') || (c == '~'))
{
return true;
}
return false;
}
bool CheckExternalFile(const char *filePath, const size_t filePathLen, const size_t maxFileSizeMb,
const bool checkOwner)
{
if ((filePathLen > PATH_MAX) || (filePathLen <= 0))
{
return ShowExceptionInfo("filePathLen out of bounds!");
}
for (size_t iLoop = 0; iLoop < filePathLen; iLoop++)
{
if (!IsValidChar(filePath[iLoop]))
{
return ShowExceptionInfo("filePath has an illegal character!");
}
}
return CheckLegality(filePath, filePathLen, maxFileSizeMb, checkOwner);
}
bool CheckExistsFile(const char *filePath, const size_t filePathLen, const size_t maxFileSizeMb, const bool checkWgroup)
{
if (filePath == NULL)
{
return false;
}
struct stat fileStat;
if (lstat(filePath, &fileStat) != 0)
{
return true;
}
if (S_ISREG(fileStat.st_mode) == 0)
{
return false;
}
g_checkWgroup = checkWgroup;
if (!CheckExternalFile(filePath, filePathLen, maxFileSizeMb, true))
{
g_checkWgroup = true;
return false;
}
g_checkWgroup = true;
return true;
}
bool CheckOpenedFile(FILE *fp, const long maxSize, const bool checkOwner)
{
if (fp == NULL)
{
return false;
}
struct stat st;
if (fstat(fileno(fp), &st) != 0)
{
return false;
}
if (S_ISREG(st.st_mode) == 0 || st.st_size > maxSize)
{
return false;
}
if (!CheckFileOwner(st, checkOwner))
{
return false;
}
return true;
}
STATIC bool CheckSymlinkOwner(const char *path)
{
struct stat linkStat;
struct stat realStat;
if (lstat(path, &linkStat) != 0)
{
return ShowExceptionInfo("failed to lstat symlink!");
}
if (stat(path, &realStat) != 0)
{
return ShowExceptionInfo("failed to stat real file of symlink!");
}
if (linkStat.st_uid != 0 || realStat.st_uid != 0)
{
return ShowExceptionInfo("symlink or its target is not owned by root!");
}
return true;
}
STATIC bool CheckFileSubset(const char *filePath, const size_t filePathLen, const size_t maxFileSizeMb)
{
const unsigned long long maxFileSizeB = maxFileSizeMb * 1024 * 1024;
int iLoop;
char *str = NULL;
if ((filePathLen > PATH_MAX) || (filePathLen <= 0))
{
str = FormatLogMessage("filePathLen out of bounds! filePath: %s", filePath != NULL ? filePath : "(null)");
Logger(str, LEVEL_ERROR, SCREEN_YES);
free(str);
str = NULL;
return ShowExceptionInfo("filePathLen out of bounds!");
}
for (iLoop = 0; iLoop < filePathLen; iLoop++)
{
if (!IsValidChar(filePath[iLoop]))
{
str = FormatLogMessage("filePath has an illegal character '%c' in path: %s", filePath[iLoop], filePath);
Logger(str, LEVEL_ERROR, SCREEN_YES);
free(str);
str = NULL;
return ShowExceptionInfo("filePath has an illegal character!");
}
}
struct stat fileStat;
if (lstat(filePath, &fileStat) != 0)
{
str = FormatLogMessage("filePath does not exist! filePath: %s", filePath);
Logger(str, LEVEL_ERROR, SCREEN_YES);
free(str);
str = NULL;
return ShowExceptionInfo("filePath does not exist!");
}
if (!g_allowLink && S_ISLNK(fileStat.st_mode) != 0)
{
if (!CheckSymlinkOwner(filePath))
{
return false;
}
}
if (fileStat.st_size >= maxFileSizeB)
{
str = FormatLogMessage("fileSize out of bounds! filePath: %s", filePath);
Logger(str, LEVEL_ERROR, SCREEN_YES);
free(str);
str = NULL;
return ShowExceptionInfo("fileSize out of bounds!");
}
return true;
}
bool GetFileSubsetAndCheck(const char *basePath, const size_t basePathLen)
{
if (basePath == NULL)
{
return ShowExceptionInfo("path is null!");
}
DIR *dir = NULL;
struct dirent *ptr = NULL;
char base[PATH_MAX] = {0};
if ((dir = opendir(basePath)) == NULL)
{
return ShowExceptionInfo("Open dir error!");
}
while ((ptr = readdir(dir)) != NULL)
{
if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
{
continue;
}
memset_s(base, PATH_MAX, 0, PATH_MAX);
if (strcpy_s(base, PATH_MAX, basePath) != 0)
{
closedir(dir);
return ShowExceptionInfo("Strcpy failed!");
}
if (strcat_sp(base, PATH_MAX, "/") != 0)
{
closedir(dir);
return ShowExceptionInfo("Strcat failed!");
}
if (strcat_sp(base, PATH_MAX, ptr->d_name) != 0)
{
closedir(dir);
return ShowExceptionInfo("Strcat failed!");
}
if (ptr->d_type == DT_REG)
{
const size_t maxFileSizeMb = 150;
if (!CheckFileSubset(base, strlen(base), maxFileSizeMb))
{
closedir(dir);
return false;
}
}
else if (!g_allowLink && ptr->d_type == DT_LNK)
{
if (!CheckSymlinkOwner(base))
{
closedir(dir);
return false;
}
}
else if (ptr->d_type == DT_DIR)
{
if (!GetFileSubsetAndCheck(base, strlen(base)))
{
closedir(dir);
return false;
}
}
}
closedir(dir);
return true;
}
bool GetAllowLink(void) { return g_allowLink; }
void SetAllowLink(bool value) { g_allowLink = value; }