Skip to content
Snippets Groups Projects
Select Git revision
  • fe6c8f47412e2327df43f512f03beee96a2e1999
  • master default protected
  • 1.31
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
  • 4.31.0
  • 4.30.1
  • 4.30.0
  • 4.29.1
  • 4.29.0
  • 4.28.0
  • 4.27.0
  • 4.26.0
  • 4.25.5
  • 4.25.4
  • 4.25.3
  • 4.25.2
  • 4.25.1
23 results

target-bob.mk

Blame
  • openat2_linux.go 4.01 KiB
    //go:build linux
    
    // Copyright (C) 2024 SUSE LLC. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    package securejoin
    
    import (
    	"errors"
    	"fmt"
    	"os"
    	"path/filepath"
    	"strings"
    
    	"golang.org/x/sys/unix"
    )
    
    var hasOpenat2 = sync_OnceValue(func() bool {
    	fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{
    		Flags:   unix.O_PATH | unix.O_CLOEXEC,
    		Resolve: unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_IN_ROOT,
    	})
    	if err != nil {
    		return false
    	}
    	_ = unix.Close(fd)
    	return true
    })
    
    func scopedLookupShouldRetry(how *unix.OpenHow, err error) bool {
    	// RESOLVE_IN_ROOT (and RESOLVE_BENEATH) can return -EAGAIN if we resolve
    	// ".." while a mount or rename occurs anywhere on the system. This could
    	// happen spuriously, or as the result of an attacker trying to mess with
    	// us during lookup.
    	//
    	// In addition, scoped lookups have a "safety check" at the end of
    	// complete_walk which will return -EXDEV if the final path is not in the
    	// root.
    	return how.Resolve&(unix.RESOLVE_IN_ROOT|unix.RESOLVE_BENEATH) != 0 &&
    		(errors.Is(err, unix.EAGAIN) || errors.Is(err, unix.EXDEV))
    }
    
    const scopedLookupMaxRetries = 10
    
    func openat2File(dir *os.File, path string, how *unix.OpenHow) (*os.File, error) {
    	fullPath := dir.Name() + "/" + path
    	// Make sure we always set O_CLOEXEC.
    	how.Flags |= unix.O_CLOEXEC
    	var tries int
    	for tries < scopedLookupMaxRetries {
    		fd, err := unix.Openat2(int(dir.Fd()), path, how)
    		if err != nil {
    			if scopedLookupShouldRetry(how, err) {
    				// We retry a couple of times to avoid the spurious errors, and
    				// if we are being attacked then returning -EAGAIN is the best
    				// we can do.
    				tries++
    				continue
    			}
    			return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: err}
    		}
    		// If we are using RESOLVE_IN_ROOT, the name we generated may be wrong.
    		// NOTE: The procRoot code MUST NOT use RESOLVE_IN_ROOT, otherwise
    		//       you'll get infinite recursion here.
    		if how.Resolve&unix.RESOLVE_IN_ROOT == unix.RESOLVE_IN_ROOT {
    			if actualPath, err := rawProcSelfFdReadlink(fd); err == nil {
    				fullPath = actualPath
    			}
    		}
    		return os.NewFile(uintptr(fd), fullPath), nil
    	}
    	return nil, &os.PathError{Op: "openat2", Path: fullPath, Err: errPossibleAttack}
    }
    
    func lookupOpenat2(root *os.File, unsafePath string, partial bool) (*os.File, string, error) {
    	if !partial {
    		file, err := openat2File(root, unsafePath, &unix.OpenHow{
    			Flags:   unix.O_PATH | unix.O_CLOEXEC,
    			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
    		})
    		return file, "", err
    	}
    	return partialLookupOpenat2(root, unsafePath)
    }
    
    // partialLookupOpenat2 is an alternative implementation of
    // partialLookupInRoot, using openat2(RESOLVE_IN_ROOT) to more safely get a
    // handle to the deepest existing child of the requested path within the root.
    func partialLookupOpenat2(root *os.File, unsafePath string) (*os.File, string, error) {
    	// TODO: Implement this as a git-bisect-like binary search.
    
    	unsafePath = filepath.ToSlash(unsafePath) // noop
    	endIdx := len(unsafePath)
    	var lastError error
    	for endIdx > 0 {
    		subpath := unsafePath[:endIdx]
    
    		handle, err := openat2File(root, subpath, &unix.OpenHow{
    			Flags:   unix.O_PATH | unix.O_CLOEXEC,
    			Resolve: unix.RESOLVE_IN_ROOT | unix.RESOLVE_NO_MAGICLINKS,
    		})
    		if err == nil {
    			// Jump over the slash if we have a non-"" remainingPath.
    			if endIdx < len(unsafePath) {
    				endIdx += 1
    			}
    			// We found a subpath!
    			return handle, unsafePath[endIdx:], lastError
    		}
    		if errors.Is(err, unix.ENOENT) || errors.Is(err, unix.ENOTDIR) {
    			// That path doesn't exist, let's try the next directory up.
    			endIdx = strings.LastIndexByte(subpath, '/')
    			lastError = err
    			continue
    		}
    		return nil, "", fmt.Errorf("open subpath: %w", err)
    	}
    	// If we couldn't open anything, the whole subpath is missing. Return a
    	// copy of the root fd so that the caller doesn't close this one by
    	// accident.
    	rootClone, err := dupFile(root)
    	if err != nil {
    		return nil, "", err
    	}
    	return rootClone, unsafePath, lastError
    }