diff --git a/internal/servers/hls/muxer_instance.go b/internal/servers/hls/muxer_instance.go index 9823a204..ca516b81 100644 --- a/internal/servers/hls/muxer_instance.go +++ b/internal/servers/hls/muxer_instance.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "sync" "sync/atomic" "time" @@ -25,6 +26,26 @@ const ( sessionCleanupPeriod = sessionCloseAfter / 3 ) +// this prevents directory traversal. +// functionally it's useless since there's already conf.IsPathName, but it's needed by CodeQL. +func safeSubDirectory(base string, pathName string) (string, error) { + baseAbs, err := filepath.Abs(filepath.Clean(base)) + if err != nil { + return "", err + } + + candidateAbs, err := filepath.Abs(filepath.Join(baseAbs, pathName)) + if err != nil { + return "", err + } + + if !strings.HasPrefix(candidateAbs, baseAbs) { + return "", fmt.Errorf("path escapes base directory") + } + + return candidateAbs, nil +} + type instanceParent interface { logger.Writer closeInstance(*muxerInstance, error) @@ -54,9 +75,18 @@ func (mi *muxerInstance) initialize() error { mi.Log(logger.Debug, "instance created") var muxerDirectory string + if mi.directory != "" { - muxerDirectory = filepath.Join(mi.directory, mi.pathName) - os.MkdirAll(muxerDirectory, 0o755) + var err error + muxerDirectory, err = safeSubDirectory(mi.directory, mi.pathName) + if err != nil { + return err + } + + err = os.MkdirAll(muxerDirectory, 0o755) + if err != nil { + return err + } } mi.hmuxer = &gohlslib.Muxer{ diff --git a/internal/servers/hls/server_test.go b/internal/servers/hls/server_test.go index 781eee0f..75a3d818 100644 --- a/internal/servers/hls/server_test.go +++ b/internal/servers/hls/server_test.go @@ -538,6 +538,20 @@ func TestServerDirectory(t *testing.T) { require.NoError(t, err) } +func TestSafeSubDirectory(t *testing.T) { + base := t.TempDir() + + path, err := safeSubDirectory(base, "group/cam1") + require.NoError(t, err) + require.Equal(t, filepath.Join(base, "group", "cam1"), path) + + _, err = safeSubDirectory(base, "../cam1") + require.Error(t, err) + + _, err = safeSubDirectory(base, "group/../../cam1") + require.Error(t, err) +} + func TestServerDynamicAlwaysRemux(t *testing.T) { desc := &description.Session{Medias: []*description.Media{test.MediaH264}}