169 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			169 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| //go:build linux || freebsd || darwin
 | |
| 
 | |
| package open
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"github.com/containers/storage/pkg/reexec"
 | |
| 	"golang.org/x/sys/unix"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	inChrootCommand = "buildah-open-in-chroot"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	reexec.Register(inChrootCommand, inChrootMain)
 | |
| }
 | |
| 
 | |
| func inChroot(requests requests) results {
 | |
| 	sock, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM, 0)
 | |
| 	if err != nil {
 | |
| 		return results{Err: fmt.Errorf("creating socket pair: %w", err).Error()}
 | |
| 	}
 | |
| 	parentSock := sock[0]
 | |
| 	childSock := sock[1]
 | |
| 	parentEnd := os.NewFile(uintptr(parentSock), "parent end of socket pair")
 | |
| 	childEnd := os.NewFile(uintptr(childSock), "child end of socket pair")
 | |
| 	cmd := reexec.Command(inChrootCommand)
 | |
| 	cmd.ExtraFiles = append(cmd.ExtraFiles, childEnd)
 | |
| 	err = cmd.Start()
 | |
| 	childEnd.Close()
 | |
| 	defer parentEnd.Close()
 | |
| 	if err != nil {
 | |
| 		return results{Err: err.Error()}
 | |
| 	}
 | |
| 	encoder := json.NewEncoder(parentEnd)
 | |
| 	if err := encoder.Encode(&requests); err != nil {
 | |
| 		return results{Err: fmt.Errorf("sending request down socket: %w", err).Error()}
 | |
| 	}
 | |
| 	if err := unix.Shutdown(parentSock, unix.SHUT_WR); err != nil {
 | |
| 		return results{Err: fmt.Errorf("finishing sending request down socket: %w", err).Error()}
 | |
| 	}
 | |
| 	b := make([]byte, 65536)
 | |
| 	oob := make([]byte, 65536)
 | |
| 	n, oobn, _, _, err := unix.Recvmsg(parentSock, b, oob, 0)
 | |
| 	if err != nil {
 | |
| 		return results{Err: fmt.Errorf("receiving message: %w", err).Error()}
 | |
| 	}
 | |
| 	if err := unix.Shutdown(parentSock, unix.SHUT_RD); err != nil {
 | |
| 		return results{Err: fmt.Errorf("finishing socket: %w", err).Error()}
 | |
| 	}
 | |
| 	if n > len(b) {
 | |
| 		return results{Err: fmt.Errorf("too much regular data: %d > %d", n, len(b)).Error()}
 | |
| 	}
 | |
| 	if oobn > len(oob) {
 | |
| 		return results{Err: fmt.Errorf("too much OOB data: %d > %d", oobn, len(oob)).Error()}
 | |
| 	}
 | |
| 	scms, err := unix.ParseSocketControlMessage(oob[:oobn])
 | |
| 	if err != nil {
 | |
| 		return results{Err: fmt.Errorf("parsing control message: %w", err).Error()}
 | |
| 	}
 | |
| 	var receivedFds []int
 | |
| 	for i := range scms {
 | |
| 		fds, err := unix.ParseUnixRights(&scms[i])
 | |
| 		if err != nil {
 | |
| 			return results{Err: fmt.Errorf("parsing rights message %d: %w", i, err).Error()}
 | |
| 		}
 | |
| 		receivedFds = append(receivedFds, fds...)
 | |
| 	}
 | |
| 	decoder := json.NewDecoder(bytes.NewReader(b[:n]))
 | |
| 	var result results
 | |
| 	if err := decoder.Decode(&result); err != nil {
 | |
| 		return results{Err: fmt.Errorf("decoding results: %w", err).Error()}
 | |
| 	}
 | |
| 	j := 0
 | |
| 	for i := range result.Open {
 | |
| 		if result.Open[i].Err == "" {
 | |
| 			if j >= len(receivedFds) {
 | |
| 				for _, fd := range receivedFds {
 | |
| 					unix.Close(fd)
 | |
| 				}
 | |
| 				return results{Err: fmt.Errorf("didn't receive enough FDs").Error()}
 | |
| 			}
 | |
| 			result.Open[i].Fd = uintptr(receivedFds[j])
 | |
| 			j++
 | |
| 		}
 | |
| 	}
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| func inChrootMain() {
 | |
| 	var theseRequests requests
 | |
| 	var theseResults results
 | |
| 	sockFd := 3
 | |
| 	sock := os.NewFile(uintptr(sockFd), "socket connection to parent process")
 | |
| 	defer sock.Close()
 | |
| 	encoder := json.NewEncoder(sock)
 | |
| 	decoder := json.NewDecoder(sock)
 | |
| 	if err := decoder.Decode(&theseRequests); err != nil {
 | |
| 		if err := encoder.Encode(results{Err: fmt.Errorf("decoding request: %w", err).Error()}); err != nil {
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 	}
 | |
| 	if theseRequests.Root != "" {
 | |
| 		if err := os.Chdir(theseRequests.Root); err != nil {
 | |
| 			if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q: %w", theseRequests.Root, err).Error()}); err != nil {
 | |
| 				os.Exit(1)
 | |
| 			}
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 		if err := unix.Chroot(theseRequests.Root); err != nil {
 | |
| 			if err := encoder.Encode(results{Err: fmt.Errorf("chrooting to %q: %w", theseRequests.Root, err).Error()}); err != nil {
 | |
| 				os.Exit(1)
 | |
| 			}
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 		if err := os.Chdir("/"); err != nil {
 | |
| 			if err := encoder.Encode(results{Err: fmt.Errorf("changing to new root: %w", err).Error()}); err != nil {
 | |
| 				os.Exit(1)
 | |
| 			}
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 	}
 | |
| 	if theseRequests.Wd != "" {
 | |
| 		if err := os.Chdir(theseRequests.Wd); err != nil {
 | |
| 			if err := encoder.Encode(results{Err: fmt.Errorf("changing to %q in chroot: %w", theseRequests.Wd, err).Error()}); err != nil {
 | |
| 				os.Exit(1)
 | |
| 			}
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 	}
 | |
| 	var fds []int
 | |
| 	for _, request := range theseRequests.Open {
 | |
| 		fd, err := unix.Open(request.Path, request.Mode, request.Perms)
 | |
| 		thisResult := result{Fd: uintptr(fd)}
 | |
| 		if err == nil {
 | |
| 			fds = append(fds, fd)
 | |
| 		} else {
 | |
| 			var errno syscall.Errno
 | |
| 			thisResult.Err = err.Error()
 | |
| 			if errors.As(err, &errno) {
 | |
| 				thisResult.Errno = errno
 | |
| 			}
 | |
| 		}
 | |
| 		theseResults.Open = append(theseResults.Open, thisResult)
 | |
| 	}
 | |
| 	rights := unix.UnixRights(fds...)
 | |
| 	inband, err := json.Marshal(&theseResults)
 | |
| 	if err != nil {
 | |
| 		if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil {
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	if err := unix.Sendmsg(sockFd, inband, rights, nil, 0); err != nil {
 | |
| 		if err := encoder.Encode(results{Err: fmt.Errorf("sending response: %w", err).Error()}); err != nil {
 | |
| 			os.Exit(1)
 | |
| 		}
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	os.Exit(0)
 | |
| }
 |