sandbox: enable sandboxing for llama.cpp processes on macOS
Signed-off-by: Jacob Howard <jacob.howard@docker.com>
This commit is contained in:
parent
229e081bc2
commit
9741e9a734
|
|
@ -9,7 +9,6 @@ import (
|
|||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
|
@ -24,6 +23,7 @@ import (
|
|||
"github.com/docker/model-runner/pkg/inference/config"
|
||||
"github.com/docker/model-runner/pkg/inference/models"
|
||||
"github.com/docker/model-runner/pkg/logging"
|
||||
"github.com/docker/model-runner/pkg/sandbox"
|
||||
"github.com/docker/model-runner/pkg/tailbuffer"
|
||||
)
|
||||
|
||||
|
|
@ -153,11 +153,15 @@ func (l *llamaCpp) Run(ctx context.Context, socket, model string, mode inference
|
|||
}
|
||||
|
||||
l.log.Infof("llamaCppArgs: %v", args)
|
||||
llamaCppProcess := exec.CommandContext(
|
||||
llamaCppProcess, err := sandbox.CommandContext(
|
||||
ctx,
|
||||
sandbox.LlamaCppTemplate,
|
||||
filepath.Join(binPath, "com.docker.llama-server"),
|
||||
args...,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create sandboxed llama.cpp process: %w", err)
|
||||
}
|
||||
llamaCppProcess.Cancel = func() error {
|
||||
if runtime.GOOS == "windows" {
|
||||
return llamaCppProcess.Process.Kill()
|
||||
|
|
@ -351,13 +355,19 @@ func (l *llamaCpp) checkGPUSupport(ctx context.Context) bool {
|
|||
if l.updatedLlamaCpp {
|
||||
binPath = l.updatedServerStoragePath
|
||||
}
|
||||
out, err := exec.CommandContext(
|
||||
llamaCppProcess, err := sandbox.CommandContext(
|
||||
ctx,
|
||||
sandbox.LlamaCppTemplate,
|
||||
filepath.Join(binPath, "com.docker.llama-server"),
|
||||
"--list-devices",
|
||||
).CombinedOutput()
|
||||
)
|
||||
if err != nil {
|
||||
l.log.Warnf("Failed to determine if llama-server is built with GPU support: %s", err)
|
||||
l.log.Warnf("Failed to create sandboxed llama.cpp process to probe GPU support: %v", err)
|
||||
return false
|
||||
}
|
||||
out, err := llamaCppProcess.CombinedOutput()
|
||||
if err != nil {
|
||||
l.log.Warnf("Failed to determine if llama-server is built with GPU support: %v", err)
|
||||
return false
|
||||
}
|
||||
sc := bufio.NewScanner(strings.NewReader(string(out)))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LlamaCppTemplate is the sandbox template to use for llama.cpp processes.
|
||||
const LlamaCppTemplate = `(version 1)
|
||||
|
||||
;;; Keep a default allow policy (because encoding things like DYLD support and
|
||||
;;; device access is quite difficult), but deny critical exploitation targets
|
||||
;;; (generally aligned with the App Sandbox entitlements that aren't on by
|
||||
;;; default). In theory we'll be subject to the Docker.app sandbox as well
|
||||
;;; (unless we're running standalone), but even Docker.app has a very privileged
|
||||
;;; sandbox, so we need additional constraints.
|
||||
;;;
|
||||
;;; Note: The following are known to be required at some level for llama.cpp
|
||||
;;; (though we could further experiment to deny certain sub-permissions):
|
||||
;;; - authorization
|
||||
;;; - darwin
|
||||
;;; - iokit
|
||||
;;; - mach
|
||||
;;; - socket
|
||||
;;; - syscall
|
||||
;;; - process
|
||||
(allow default)
|
||||
|
||||
;;; Deny network access, except for our IPC sockets.
|
||||
(deny network*)
|
||||
(allow network-bind network-inbound
|
||||
(regex #"inference-runner-[0-9]+\.sock$"))
|
||||
|
||||
;;; Deny access to the camera and microphone.
|
||||
(deny device*)
|
||||
|
||||
;;; Deny access to NVRAM settings.
|
||||
(deny nvram*)
|
||||
|
||||
;;; Deny access to system-level privileges.
|
||||
(deny system*)
|
||||
|
||||
;;; Deny access to job creation.
|
||||
(deny job-creation)
|
||||
|
||||
;;; Don't allow new executable code to be created in memory at runtime.
|
||||
(deny dynamic-code-generation)
|
||||
|
||||
;;; Disable access to user preferences.
|
||||
(deny user-preference*)
|
||||
|
||||
;;; Restrict file access.
|
||||
;;; NOTE: For some reason, the (home-subpath "...") predicate used in system
|
||||
;;; sandbox profiles doesn't work with sandbox-exec.
|
||||
;;; NOTE: We have to allow access to the working directory for standalone mode.
|
||||
;;; NOTE: For some reason (deny file-read*) really doesn't like to play nice
|
||||
;;; with llama.cpp, so for that reason we'll avoid a blanket ban and just ban
|
||||
;;; directories that might contain sensitive data.
|
||||
(deny file-map-executable)
|
||||
(deny file-write*)
|
||||
(deny file-read*
|
||||
(subpath "/Applications")
|
||||
(subpath "/private/etc")
|
||||
(subpath "/Library")
|
||||
(subpath "/Users")
|
||||
(subpath "/Volumes"))
|
||||
(allow file-read* file-map-executable
|
||||
(subpath "/usr")
|
||||
(subpath "/System")
|
||||
(subpath "/Applications/Docker.app/Contents/Resources/model-runner")
|
||||
(subpath "[HOMEDIR]/.docker/bin/inference")
|
||||
(subpath "[HOMEDIR]/.docker/bin/lib"))
|
||||
(allow file-write*
|
||||
(regex #"inference-runner-[0-9]+\.sock$")
|
||||
(literal "/dev/null")
|
||||
(subpath "/private/var")
|
||||
(subpath "[WORKDIR]"))
|
||||
(allow file-read*
|
||||
(subpath "[WORKDIR]")
|
||||
(subpath "[HOMEDIR]/.docker/models"))
|
||||
`
|
||||
|
||||
// CommandContext creates a sandboxed version of an os/exec.Cmd. On Darwin, we
|
||||
// use the sandbox-exec command to wrap the process.
|
||||
func CommandContext(ctx context.Context, template, name string, args ...string) (*exec.Cmd, error) {
|
||||
// Look up the user's home directory.
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to lookup user: %w", err)
|
||||
}
|
||||
|
||||
// Look up the working directory.
|
||||
currentDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to determine working directory: %w", err)
|
||||
}
|
||||
|
||||
// Compute the profile. Switch to text/template if this gets more complex.
|
||||
profile := strings.ReplaceAll(template, "[HOMEDIR]", currentUser.HomeDir)
|
||||
profile = strings.ReplaceAll(profile, "[WORKDIR]", currentDirectory)
|
||||
|
||||
// Create the sandboxed process.
|
||||
sandboxedArgs := make([]string, 0, len(args)+3)
|
||||
sandboxedArgs = append(sandboxedArgs, "-p", profile, name)
|
||||
sandboxedArgs = append(sandboxedArgs, args...)
|
||||
return exec.CommandContext(ctx, "sandbox-exec", sandboxedArgs...), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
//go:build !darwin && !windows
|
||||
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// LlamaCppTemplate is the sandbox template to use for llama.cpp processes.
|
||||
const LlamaCppTemplate = ``
|
||||
|
||||
// CommandContext creates a sandboxed version of an os/exec.Cmd. On Linux, we
|
||||
// don't currently support sandboxing since we already run inside containers, so
|
||||
// this function is a direct passthrough.
|
||||
func CommandContext(ctx context.Context, template, name string, args ...string) (*exec.Cmd, error) {
|
||||
return exec.CommandContext(ctx, name, args...), nil
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// LlamaCppTemplate is the sandbox template to use for llama.cpp processes.
|
||||
const LlamaCppTemplate = ``
|
||||
|
||||
// CommandContext creates a sandboxed version of an os/exec.Cmd. On Windows, we
|
||||
// use the wsb exec command to wrap the process.
|
||||
func CommandContext(ctx context.Context, template, name string, args ...string) (*exec.Cmd, error) {
|
||||
// TODO: Implement.
|
||||
return exec.CommandContext(ctx, name, args...), nil
|
||||
}
|
||||
Loading…
Reference in New Issue