Skip to content

Commit 0130084

Browse files
diw-zw照微
andauthored
Deal with codeQL alert: Uncontrolled data used in path expression (#257)
* Add CodeQL analysis workflow configuration * Deal with codeQL alert: Uncontrolled data used in path expression * remove binary * CodeQL: Incorrect conversion between integer types * deal with invalid component id * Remove codeQL workflow --------- Co-authored-by: 照微 <wd528567@alibaba-inc.com>
1 parent f9d3be7 commit 0130084

5 files changed

Lines changed: 244 additions & 239 deletions

File tree

cmd/cli/cmd/llm/benchmark/dashboard/handlers.go

Lines changed: 109 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,17 @@ type Server struct {
4242
}
4343

4444
// NewServer creates a new HTTP server instance.
45-
func NewServer(dataDir string) *Server {
45+
func NewServer(dataDir string) (*Server, error) {
46+
baseAbs, err := filepath.Abs(dataDir)
47+
if err != nil {
48+
return nil, err
49+
}
4650
s := &Server{
47-
dataDir: dataDir,
51+
dataDir: baseAbs,
4852
mux: http.NewServeMux(),
4953
}
5054
s.registerRoutes()
51-
return s
55+
return s, nil
5256
}
5357

5458
// registerRoutes sets up all HTTP routes.
@@ -101,12 +105,6 @@ func (s *Server) handleExperimentRoutes(w http.ResponseWriter, r *http.Request)
101105

102106
experimentName := parts[0]
103107

104-
// Validate experiment name to prevent path traversal
105-
if !isValidName(experimentName) {
106-
s.writeError(w, http.StatusBadRequest, "Invalid experiment name")
107-
return
108-
}
109-
110108
if len(parts) == 1 {
111109
// GET /api/experiments/{name}
112110
s.handleGetExperiment(w, r, experimentName)
@@ -177,9 +175,12 @@ func (s *Server) handleGetResult(w http.ResponseWriter, r *http.Request, experim
177175
return
178176
}
179177

180-
// Validate inputs to prevent path traversal
181-
if !isValidName(experimentName) || !isValidName(fileName) {
182-
s.writeError(w, http.StatusBadRequest, "Invalid experiment name or filename")
178+
if strings.Contains(experimentName, "/") || strings.Contains(experimentName, "\\") || strings.Contains(experimentName, "..") {
179+
s.writeError(w, http.StatusBadRequest, "Invalid experiment name")
180+
return
181+
}
182+
if strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") || strings.Contains(fileName, "..") {
183+
s.writeError(w, http.StatusBadRequest, "Invalid result filename")
183184
return
184185
}
185186

@@ -194,10 +195,17 @@ func (s *Server) handleGetResult(w http.ResponseWriter, r *http.Request, experim
194195
return
195196
}
196197

197-
filePath := filepath.Join(s.dataDir, experimentName, fileName)
198+
// Local safety check: validate path is within dataDir
199+
// This provides explicit validation that static analysis tools can recognize
200+
expDir, ok := isSafeRelativePath(s.dataDir, experimentName)
201+
if !ok {
202+
s.writeError(w, http.StatusForbidden, "Access denied")
203+
return
204+
}
198205

199-
// Security check: ensure the path is within dataDir
200-
if !s.isPathSafe(filePath) {
206+
// Validate fileName is safe and construct the final file path
207+
filePath, ok := isSafeRelativePath(expDir, fileName)
208+
if !ok {
201209
s.writeError(w, http.StatusForbidden, "Access denied")
202210
return
203211
}
@@ -230,10 +238,12 @@ func (s *Server) handleGetImage(w http.ResponseWriter, r *http.Request, experime
230238
s.writeError(w, http.StatusMethodNotAllowed, "Method not allowed")
231239
return
232240
}
233-
234-
// Validate inputs to prevent path traversal
235-
if !isValidName(experimentName) || !isValidName(fileName) {
236-
s.writeError(w, http.StatusBadRequest, "Invalid experiment name or filename")
241+
if strings.Contains(experimentName, "/") || strings.Contains(experimentName, "\\") || strings.Contains(experimentName, "..") {
242+
s.writeError(w, http.StatusBadRequest, "Invalid experiment name")
243+
return
244+
}
245+
if strings.Contains(fileName, "/") || strings.Contains(fileName, "\\") || strings.Contains(fileName, "..") {
246+
s.writeError(w, http.StatusBadRequest, "Invalid result filename")
237247
return
238248
}
239249

@@ -247,10 +257,17 @@ func (s *Server) handleGetImage(w http.ResponseWriter, r *http.Request, experime
247257
return
248258
}
249259

250-
filePath := filepath.Join(s.dataDir, experimentName, fileName)
260+
// Local safety check: validate path is within dataDir
261+
// This provides explicit validation that static analysis tools can recognize
262+
expDir, ok := isSafeRelativePath(s.dataDir, experimentName)
263+
if !ok {
264+
s.writeError(w, http.StatusForbidden, "Access denied")
265+
return
266+
}
251267

252-
// Security check
253-
if !s.isPathSafe(filePath) {
268+
// Validate fileName is safe and construct the final file path
269+
filePath, ok := isSafeRelativePath(expDir, fileName)
270+
if !ok {
254271
s.writeError(w, http.StatusForbidden, "Access denied")
255272
return
256273
}
@@ -401,18 +418,35 @@ func (s *Server) listExperiments() ([]Experiment, error) {
401418

402419
// getExperiment returns details of a specific experiment.
403420
func (s *Server) getExperiment(name string) (*Experiment, error) {
404-
// Validate name to prevent path traversal
405-
if !isValidName(name) {
421+
if strings.Contains(name, "/") || strings.Contains(name, "\\") || strings.Contains(name, "..") {
406422
return nil, os.ErrNotExist
407423
}
408-
409-
expDir := filepath.Join(s.dataDir, name)
410-
411-
// Security check: ensure the path is within dataDir
412-
if !s.isPathSafe(expDir) {
424+
// Resolve symlinks for both the base directory and the experiment directory
425+
// before checking containment, so a symlink under s.dataDir cannot escape it.
426+
baseDir, err := filepath.EvalSymlinks(s.dataDir)
427+
if err != nil {
428+
return nil, err
429+
}
430+
baseDir, err = filepath.Abs(baseDir)
431+
if err != nil {
432+
return nil, err
433+
}
434+
expDirJoined := filepath.Join(s.dataDir, name)
435+
expDir, err := filepath.EvalSymlinks(expDirJoined)
436+
if err != nil {
437+
return nil, err
438+
}
439+
expDir, err = filepath.Abs(expDir)
440+
if err != nil {
441+
return nil, err
442+
}
443+
relPath, err := filepath.Rel(baseDir, expDir)
444+
if err != nil {
445+
return nil, err
446+
}
447+
if relPath == ".." || strings.HasPrefix(relPath, ".."+string(os.PathSeparator)) {
413448
return nil, os.ErrNotExist
414449
}
415-
416450
info, err := os.Stat(expDir)
417451
if err != nil {
418452
return nil, err
@@ -460,15 +494,14 @@ func (s *Server) getExperiment(name string) (*Experiment, error) {
460494

461495
// getResultSummaries returns summaries of all result files in an experiment.
462496
func (s *Server) getResultSummaries(experimentName string) ([]ResultSummary, error) {
463-
// Validate name to prevent path traversal
464-
if !isValidName(experimentName) {
497+
if strings.Contains(experimentName, "/") || strings.Contains(experimentName, "\\") || strings.Contains(experimentName, "..") {
465498
return nil, os.ErrNotExist
466499
}
467500

468-
expDir := filepath.Join(s.dataDir, experimentName)
469-
470-
// Security check: ensure the path is within dataDir
471-
if !s.isPathSafe(expDir) {
501+
// Local safety check: validate path is within dataDir
502+
// This provides explicit validation that static analysis tools can recognize
503+
expDir, ok := isSafeRelativePath(s.dataDir, experimentName)
504+
if !ok {
472505
return nil, os.ErrNotExist
473506
}
474507

@@ -551,64 +584,58 @@ func isValidName(name string) bool {
551584
return true
552585
}
553586

554-
// isPathSafe checks if the given path is within the data directory.
555-
// It resolves symlinks before checking containment to prevent symlink-based path traversal attacks.
556-
func (s *Server) isPathSafe(path string) bool {
557-
// Clean the path to resolve .. and . segments
558-
cleanPath := filepath.Clean(path)
559-
560-
// Get absolute paths
561-
absPath, err := filepath.Abs(cleanPath)
562-
if err != nil {
563-
return false
587+
// isSafeRelativePath validates that subpath is safe relative to baseDir and returns the absolute path.
588+
// It checks that the joined and normalized path is still within baseDir.
589+
// This provides a local, explicit safety check that static analysis tools can recognize.
590+
func isSafeRelativePath(absBaseDir, subpath string) (string, bool) {
591+
// First validate the subpath contains only safe characters
592+
if !isValidName(subpath) {
593+
return "", false
564594
}
565-
absDataDir, err := filepath.Abs(s.dataDir)
595+
596+
// Join and clean the path
597+
joined := filepath.Join(absBaseDir, subpath)
598+
absPath, err := filepath.Abs(joined)
566599
if err != nil {
567-
return false
600+
return "", false
568601
}
569602

570-
// Resolve the data directory (must exist)
571-
resolvedDataDir, err := filepath.EvalSymlinks(absDataDir)
603+
// Resolve symlinks for both base dir and target path
604+
// This prevents symlink-based path traversal attacks
605+
evalBaseDir, err := filepath.EvalSymlinks(absBaseDir)
572606
if err != nil {
573-
return false
607+
return "", false
574608
}
575-
dataDirPrefix := resolvedDataDir + string(filepath.Separator)
576609

577-
// Check if the path exists
578-
_, err = os.Stat(absPath)
579-
if err == nil {
580-
// Path exists - fully resolve it and check containment
581-
resolvedPath, err := filepath.EvalSymlinks(absPath)
610+
// For the target path, we need to handle the case where it may not exist yet
611+
// Try to evaluate symlinks, but if the path doesn't exist, we'll validate
612+
// against the parent directory
613+
evalPath, err := filepath.EvalSymlinks(absPath)
614+
if err != nil {
615+
// Path doesn't exist - validate against base dir
616+
// Use filepath.Rel to ensure the path is relative to base dir
617+
relPath, err := filepath.Rel(absBaseDir, absPath)
582618
if err != nil {
583-
return false
619+
return "", false
584620
}
585-
return strings.HasPrefix(resolvedPath+string(filepath.Separator), dataDirPrefix)
586-
}
587-
588-
// Path doesn't exist - check that the nearest existing ancestor is within dataDir
589-
// We can only verify the safety of existing directories; non-existent portions
590-
// will be validated when actually created
591-
current := filepath.Dir(absPath)
592-
for {
593-
// Check if current directory exists
594-
_, err := os.Stat(current)
595-
if err == nil {
596-
// Found an existing ancestor - resolve it and verify it's within dataDir
597-
resolvedCurrent, err := filepath.EvalSymlinks(current)
598-
if err != nil {
599-
return false
600-
}
601-
return strings.HasPrefix(resolvedCurrent+string(filepath.Separator), dataDirPrefix)
621+
// Check if relative path escapes base dir
622+
if strings.HasPrefix(relPath, "..") || relPath == ".." {
623+
return "", false
602624
}
625+
return absPath, true
626+
}
603627

604-
// Current doesn't exist, go to parent
605-
parent := filepath.Dir(current)
606-
if parent == current {
607-
// Reached root without finding any existing directory
608-
return false
609-
}
610-
current = parent
628+
// Path exists - verify it's within the resolved base dir
629+
relPath, err := filepath.Rel(evalBaseDir, evalPath)
630+
if err != nil {
631+
return "", false
632+
}
633+
// Check if relative path escapes base dir
634+
if strings.HasPrefix(relPath, "..") || relPath == ".." {
635+
return "", false
611636
}
637+
638+
return evalPath, true
612639
}
613640

614641
// writeJSON writes a JSON response.

0 commit comments

Comments
 (0)