python.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package python
  2. import (
  3. _ "embed"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "strings"
  8. "syscall"
  9. "time"
  10. "github.com/google/uuid"
  11. "github.com/langgenius/dify-sandbox/internal/core/runner"
  12. "github.com/langgenius/dify-sandbox/internal/static"
  13. )
  14. type PythonRunner struct {
  15. runner.Runner
  16. runner.SeccompRunner
  17. }
  18. //go:embed prescript.py
  19. var python_sandbox_fs []byte
  20. //go:embed python.so
  21. var python_lib []byte
  22. func (p *PythonRunner) Run(code string, timeout time.Duration, stdin []byte) (chan []byte, chan []byte, chan bool, error) {
  23. // check if libpython.so exists
  24. if _, err := os.Stat("/tmp/sandbox-python/python.so"); os.IsNotExist(err) {
  25. err := os.MkdirAll("/tmp/sandbox-python", 0755)
  26. if err != nil {
  27. return nil, nil, nil, err
  28. }
  29. err = os.WriteFile("/tmp/sandbox-python/python.so", python_lib, 0755)
  30. if err != nil {
  31. return nil, nil, nil, err
  32. }
  33. }
  34. // create a tmp dir and copy the python script
  35. temp_code_name := strings.ReplaceAll(uuid.New().String(), "-", "_")
  36. temp_code_name = strings.ReplaceAll(temp_code_name, "/", ".")
  37. temp_code_path := fmt.Sprintf("/tmp/code/%s.py", temp_code_name)
  38. err := os.MkdirAll("/tmp/code", 0755)
  39. if err != nil {
  40. return nil, nil, nil, err
  41. }
  42. err = os.WriteFile(temp_code_path, []byte(code), 0755)
  43. if err != nil {
  44. return nil, nil, nil, err
  45. }
  46. stdout := make(chan []byte, 1)
  47. stderr := make(chan []byte, 1)
  48. done_chan := make(chan bool, 1)
  49. err = p.WithTempDir([]string{
  50. temp_code_path,
  51. "/tmp/sandbox-python/python.so",
  52. }, func(root_path string) error {
  53. var pipe_fds [2]int
  54. // create stdout pipe
  55. err = syscall.Pipe2(pipe_fds[0:], syscall.O_CLOEXEC)
  56. if err != nil {
  57. return err
  58. }
  59. stdout_reader, stdout_writer := pipe_fds[0], pipe_fds[1]
  60. // create stderr pipe
  61. err = syscall.Pipe2(pipe_fds[0:], syscall.O_CLOEXEC)
  62. if err != nil {
  63. return err
  64. }
  65. stderr_reader, stderr_writer := pipe_fds[0], pipe_fds[1]
  66. // create a new process
  67. pid, _, errno := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
  68. if errno != 0 {
  69. return fmt.Errorf("failed to fork: %v", errno)
  70. }
  71. if pid == 0 {
  72. // child process
  73. syscall.Close(stdout_reader)
  74. syscall.Close(stderr_reader)
  75. // dup the stdout and stderr
  76. syscall.Dup2(stdout_writer, int(os.Stdout.Fd()))
  77. syscall.Dup2(stderr_writer, int(os.Stderr.Fd()))
  78. err := syscall.Exec(
  79. "/usr/bin/python3",
  80. []string{
  81. "/usr/bin/python3",
  82. "-c",
  83. string(python_sandbox_fs),
  84. temp_code_path,
  85. strconv.Itoa(static.SANDBOX_USER_UID),
  86. strconv.Itoa(static.SANDBOX_GROUP_ID),
  87. },
  88. nil,
  89. )
  90. if err != nil {
  91. stderr <- []byte(fmt.Sprintf("failed to exec: %v", err))
  92. return nil
  93. }
  94. } else {
  95. syscall.Close(stdout_writer)
  96. syscall.Close(stderr_writer)
  97. // read the output
  98. go func() {
  99. buf := make([]byte, 1024)
  100. for {
  101. n, err := syscall.Read(stdout_reader, buf)
  102. if err != nil {
  103. break
  104. }
  105. stdout <- buf[:n]
  106. }
  107. }()
  108. // read the error
  109. go func() {
  110. buf := make([]byte, 1024)
  111. for {
  112. n, err := syscall.Read(stderr_reader, buf)
  113. if err != nil {
  114. break
  115. }
  116. stderr <- buf[:n]
  117. }
  118. }()
  119. // wait for the process to finish
  120. done := make(chan error, 1)
  121. go func() {
  122. var status syscall.WaitStatus
  123. _, err := syscall.Wait4(int(pid), &status, 0, nil)
  124. if err != nil {
  125. done <- err
  126. return
  127. }
  128. done <- nil
  129. }()
  130. go func() {
  131. for {
  132. select {
  133. case <-time.After(timeout):
  134. // kill the process
  135. syscall.Kill(int(pid), syscall.SIGKILL)
  136. stderr <- []byte("timeout\n")
  137. case err := <-done:
  138. if err != nil {
  139. stderr <- []byte(fmt.Sprintf("error: %v\n", err))
  140. }
  141. os.Remove(temp_code_path)
  142. os.RemoveAll(root_path)
  143. os.Remove(root_path)
  144. syscall.Close(stdout_reader)
  145. syscall.Close(stderr_reader)
  146. done_chan <- true
  147. return
  148. }
  149. }
  150. }()
  151. }
  152. return nil
  153. })
  154. if err != nil {
  155. return nil, nil, nil, err
  156. }
  157. return stdout, stderr, done_chan, nil
  158. }