Cherry-pick MM-66789: Restrict log downloads to a root path for support packets (#35164)

Automatic Merge
Этот коммит содержится в:
Doug Lauder
2026-02-02 14:23:28 -05:00
коммит произвёл GitHub
родитель d2594e5046
Коммит 463f7a0511
13 изменённых файлов: 1050 добавлений и 7 удалений

Просмотреть файл

@@ -6,11 +6,13 @@ package config
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/utils"
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
)
@@ -127,6 +129,123 @@ func GetLogSettingsFromNotificationsLogSettings(notificationLogSettings *model.N
return settings
}
// GetLogRootPath returns the root directory for all log files.
// This is used for security validation to prevent arbitrary file reads via advanced logging.
// The logging root is determined by:
// 1. MM_LOG_PATH environment variable (if set and non-empty)
// 2. The default "logs" directory (found relative to the binary)
func GetLogRootPath() string {
// Check environment variable first
if envPath := os.Getenv("MM_LOG_PATH"); envPath != "" {
absPath, err := filepath.Abs(envPath)
if err == nil {
return absPath
}
}
// Fall back to default logs directory
logsDir, _ := fileutils.FindDir("logs")
absPath, err := filepath.Abs(logsDir)
if err != nil {
return logsDir
}
return absPath
}
// ValidateLogFilePath validates that a log file path is within the logging root directory.
// This prevents arbitrary file read/write vulnerabilities in logging configuration.
// The logging root is determined by MM_LOG_PATH environment variable or the configured log directory.
func ValidateLogFilePath(filePath string, loggingRoot string) error {
// Resolve file path to absolute
absPath, err := filepath.Abs(filePath)
if err != nil {
return fmt.Errorf("cannot resolve path %s: %w", filePath, err)
}
// Resolve symlinks to prevent bypass via symlink attacks
realPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
// If file doesn't exist, still validate the intended path
if !os.IsNotExist(err) {
return fmt.Errorf("cannot resolve symlinks for %s: %w", absPath, err)
}
} else {
absPath = realPath
}
// Resolve logging root to absolute
absRoot, err := filepath.Abs(loggingRoot)
if err != nil {
return fmt.Errorf("cannot resolve logging root %s: %w", loggingRoot, err)
}
// Ensure root has trailing separator for proper prefix matching
// This prevents /tmp/log matching /tmp/logger
rootWithSep := absRoot
if !strings.HasSuffix(rootWithSep, string(filepath.Separator)) {
rootWithSep += string(filepath.Separator)
}
// Check if file is within the logging root
// Allow exact match (absPath == absRoot) or proper prefix match
if absPath != absRoot && !strings.HasPrefix(absPath, rootWithSep) {
return fmt.Errorf("path %s is outside logging root %s", filePath, absRoot)
}
return nil
}
// WarnIfLogPathsOutsideRoot validates log file paths in the config and logs errors for paths outside the logging root.
// This is called during config save to identify configurations that will cause server startup to fail in a future version.
// Currently only logs errors; in a future version this will block server startup.
func WarnIfLogPathsOutsideRoot(cfg *model.Config) {
loggingRoot := GetLogRootPath()
// Check LogSettings.AdvancedLoggingJSON
if !utils.IsEmptyJSON(cfg.LogSettings.AdvancedLoggingJSON) {
validateAdvancedLoggingConfig(cfg.LogSettings.AdvancedLoggingJSON, "LogSettings.AdvancedLoggingJSON", loggingRoot)
}
// Check NotificationLogSettings.AdvancedLoggingJSON
if !utils.IsEmptyJSON(cfg.NotificationLogSettings.AdvancedLoggingJSON) {
validateAdvancedLoggingConfig(cfg.NotificationLogSettings.AdvancedLoggingJSON, "NotificationLogSettings.AdvancedLoggingJSON", loggingRoot)
}
// Check ExperimentalAuditSettings.AdvancedLoggingJSON
if !utils.IsEmptyJSON(cfg.ExperimentalAuditSettings.AdvancedLoggingJSON) {
validateAdvancedLoggingConfig(cfg.ExperimentalAuditSettings.AdvancedLoggingJSON, "ExperimentalAuditSettings.AdvancedLoggingJSON", loggingRoot)
}
}
func validateAdvancedLoggingConfig(loggingJSON json.RawMessage, configName string, loggingRoot string) {
logCfg := make(mlog.LoggerConfiguration)
if err := json.Unmarshal(loggingJSON, &logCfg); err != nil {
return
}
for targetName, target := range logCfg {
if target.Type != "file" {
continue
}
var fileOption struct {
Filename string `json:"filename"`
}
if err := json.Unmarshal(target.Options, &fileOption); err != nil {
continue
}
if err := ValidateLogFilePath(fileOption.Filename, loggingRoot); err != nil {
mlog.Error("Log file path in logging config is outside logging root directory. This configuration will cause server startup to fail in a future version. To fix, set MM_LOG_PATH environment variable to a parent directory containing all log paths, or move log files to the configured logging root.",
mlog.String("config_section", configName),
mlog.String("target", targetName),
mlog.String("path", fileOption.Filename),
mlog.String("logging_root", loggingRoot),
mlog.Err(err))
}
}
}
func makeSimpleConsoleTarget(level string, outputJSON bool, color bool) (mlog.TargetCfg, error) {
levels, err := stdLevels(level)
if err != nil {