app: integrate interrupt forwarding
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 32s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 1m58s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 2m53s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 3m53s
				
			
		
			
				
	
				Test / Planterette (push) Successful in 3m53s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 4m31s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m19s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 32s
				
			Test / Sandbox (push) Successful in 1m58s
				
			Test / Hakurei (push) Successful in 2m53s
				
			Test / Sandbox (race detector) (push) Successful in 3m53s
				
			Test / Planterette (push) Successful in 3m53s
				
			Test / Hakurei (race detector) (push) Successful in 4m31s
				
			Test / Flake checks (push) Successful in 1m19s
				
			This significantly increases usability of command line tools running through hakurei. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									ddf48a6c22
								
							
						
					
					
						commit
						b43d104680
					
				| @ -256,8 +256,10 @@ App | |||||||
|     ], |     ], | ||||||
|     "container": { |     "container": { | ||||||
|       "hostname": "localhost", |       "hostname": "localhost", | ||||||
|  |       "immediate_termination": true, | ||||||
|       "seccomp_flags": 1, |       "seccomp_flags": 1, | ||||||
|       "seccomp_presets": 1, |       "seccomp_presets": 1, | ||||||
|  |       "seccomp_compat": true, | ||||||
|       "devel": true, |       "devel": true, | ||||||
|       "userns": true, |       "userns": true, | ||||||
|       "net": true, |       "net": true, | ||||||
| @ -382,8 +384,10 @@ App | |||||||
|   ], |   ], | ||||||
|   "container": { |   "container": { | ||||||
|     "hostname": "localhost", |     "hostname": "localhost", | ||||||
|  |     "immediate_termination": true, | ||||||
|     "seccomp_flags": 1, |     "seccomp_flags": 1, | ||||||
|     "seccomp_presets": 1, |     "seccomp_presets": 1, | ||||||
|  |     "seccomp_compat": true, | ||||||
|     "devel": true, |     "devel": true, | ||||||
|     "userns": true, |     "userns": true, | ||||||
|     "net": true, |     "net": true, | ||||||
| @ -562,8 +566,10 @@ func Test_printPs(t *testing.T) { | |||||||
|       ], |       ], | ||||||
|       "container": { |       "container": { | ||||||
|         "hostname": "localhost", |         "hostname": "localhost", | ||||||
|  |         "immediate_termination": true, | ||||||
|         "seccomp_flags": 1, |         "seccomp_flags": 1, | ||||||
|         "seccomp_presets": 1, |         "seccomp_presets": 1, | ||||||
|  |         "seccomp_compat": true, | ||||||
|         "devel": true, |         "devel": true, | ||||||
|         "userns": true, |         "userns": true, | ||||||
|         "net": true, |         "net": true, | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ type ( | |||||||
| 		// container hostname | 		// container hostname | ||||||
| 		Hostname string `json:"hostname,omitempty"` | 		Hostname string `json:"hostname,omitempty"` | ||||||
| 
 | 
 | ||||||
|  | 		// do not interrupt and wait for initial process during termination | ||||||
|  | 		ImmediateTermination bool `json:"immediate_termination,omitempty"` | ||||||
| 		// extra seccomp flags | 		// extra seccomp flags | ||||||
| 		SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"` | 		SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"` | ||||||
| 		// extra seccomp presets | 		// extra seccomp presets | ||||||
|  | |||||||
| @ -62,8 +62,10 @@ func Template() *Config { | |||||||
| 			Userns:               true, | 			Userns:               true, | ||||||
| 			Net:                  true, | 			Net:                  true, | ||||||
| 			Device:               true, | 			Device:               true, | ||||||
|  | 			ImmediateTermination: true, | ||||||
| 			SeccompFlags:         seccomp.AllowMultiarch, | 			SeccompFlags:         seccomp.AllowMultiarch, | ||||||
| 			SeccompPresets:       seccomp.PresetExt, | 			SeccompPresets:       seccomp.PresetExt, | ||||||
|  | 			SeccompCompat:        true, | ||||||
| 			Tty:                  true, | 			Tty:                  true, | ||||||
| 			Multiarch:            true, | 			Multiarch:            true, | ||||||
| 			MapRealUID:           true, | 			MapRealUID:           true, | ||||||
|  | |||||||
| @ -80,8 +80,10 @@ func TestTemplate(t *testing.T) { | |||||||
| 	], | 	], | ||||||
| 	"container": { | 	"container": { | ||||||
| 		"hostname": "localhost", | 		"hostname": "localhost", | ||||||
|  | 		"immediate_termination": true, | ||||||
| 		"seccomp_flags": 1, | 		"seccomp_flags": 1, | ||||||
| 		"seccomp_presets": 1, | 		"seccomp_presets": 1, | ||||||
|  | 		"seccomp_compat": true, | ||||||
| 		"devel": true, | 		"devel": true, | ||||||
| 		"userns": true, | 		"userns": true, | ||||||
| 		"net": true, | 		"net": true, | ||||||
|  | |||||||
| @ -144,6 +144,7 @@ var testCasesNixos = []sealTestCase{ | |||||||
| 				Tmpfs("/var/run/nscd", 8192, 0755), | 				Tmpfs("/var/run/nscd", 8192, 0755), | ||||||
| 			SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel, | 			SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel, | ||||||
| 			HostNet:        true, | 			HostNet:        true, | ||||||
|  | 			ForwardCancel:  true, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  | |||||||
| @ -71,6 +71,7 @@ var testCasesPd = []sealTestCase{ | |||||||
| 			SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel, | 			SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel, | ||||||
| 			HostNet:        true, | 			HostNet:        true, | ||||||
| 			RetainSession:  true, | 			RetainSession:  true, | ||||||
|  | 			ForwardCancel:  true, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| @ -220,6 +221,7 @@ var testCasesPd = []sealTestCase{ | |||||||
| 			SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel, | 			SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel, | ||||||
| 			HostNet:        true, | 			HostNet:        true, | ||||||
| 			RetainSession:  true, | 			RetainSession:  true, | ||||||
|  | 			ForwardCancel:  true, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  | |||||||
| @ -32,6 +32,10 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain | |||||||
| 		SeccompPresets: s.SeccompPresets, | 		SeccompPresets: s.SeccompPresets, | ||||||
| 		RetainSession:  s.Tty, | 		RetainSession:  s.Tty, | ||||||
| 		HostNet:        s.Net, | 		HostNet:        s.Net, | ||||||
|  | 
 | ||||||
|  | 		// the container is canceled when shim is requested to exit or receives an interrupt or termination signal; | ||||||
|  | 		// this behaviour is implemented in the shim | ||||||
|  | 		ForwardCancel: !s.ImmediateTermination, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	{ | 	{ | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import ( | |||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"os/signal" | 	"os/signal" | ||||||
| 	"runtime" | 	"runtime" | ||||||
|  | 	"sync/atomic" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| @ -41,6 +42,10 @@ const ( | |||||||
| 	ShimExitRequest = 254 | 	ShimExitRequest = 254 | ||||||
| 	// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal. | 	// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal. | ||||||
| 	ShimExitOrphan = 3 | 	ShimExitOrphan = 3 | ||||||
|  | 
 | ||||||
|  | 	// ShimWaitDelay is the duration to wait after interrupting a container's initial process | ||||||
|  | 	// before the container is fully killed off. | ||||||
|  | 	ShimWaitDelay = 5 * time.Second | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ShimMain is the main function of the shim process and runs as the unconstrained target user. | // ShimMain is the main function of the shim process and runs as the unconstrained target user. | ||||||
| @ -86,6 +91,7 @@ func ShimMain() { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// signal handler outcome | 	// signal handler outcome | ||||||
|  | 	var cancelContainer atomic.Pointer[context.CancelFunc] | ||||||
| 	go func() { | 	go func() { | ||||||
| 		buf := make([]byte, 1) | 		buf := make([]byte, 1) | ||||||
| 		for { | 		for { | ||||||
| @ -94,23 +100,30 @@ func ShimMain() { | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			switch buf[0] { | 			switch buf[0] { | ||||||
| 			case 0: | 			case 0: // got SIGCONT from monitor: shim exit requested | ||||||
|  | 				if fp := cancelContainer.Load(); params.Container.ForwardCancel && fp != nil && *fp != nil { | ||||||
|  | 					(*fp)() | ||||||
|  | 					// shim now bound by ShimWaitDelay, implemented below | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// setup has not completed, terminate immediately | ||||||
| 				hlog.Resume() | 				hlog.Resume() | ||||||
| 				os.Exit(ShimExitRequest) | 				os.Exit(ShimExitRequest) | ||||||
| 				return | 				return | ||||||
| 
 | 
 | ||||||
| 			case 1: | 			case 1: // got SIGCONT after adoption: monitor died before delivering signal | ||||||
| 				hlog.BeforeExit() | 				hlog.BeforeExit() | ||||||
| 				os.Exit(ShimExitOrphan) | 				os.Exit(ShimExitOrphan) | ||||||
| 				return | 				return | ||||||
| 
 | 
 | ||||||
| 			case 2: | 			case 2: // unreachable | ||||||
| 				log.Println("sa_sigaction got invalid siginfo") | 				log.Println("sa_sigaction got invalid siginfo") | ||||||
| 
 | 
 | ||||||
| 			case 3: | 			case 3: // got SIGCONT from unexpected process: hopefully the terminal driver | ||||||
| 				log.Println("got SIGCONT from unexpected process") | 				log.Println("got SIGCONT from unexpected process") | ||||||
| 
 | 
 | ||||||
| 			default: | 			default: // unreachable | ||||||
| 				log.Fatalf("got invalid message %d from signal handler", buf[0]) | 				log.Fatalf("got invalid message %d from signal handler", buf[0]) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @ -146,12 +159,11 @@ func ShimMain() { | |||||||
| 		name = params.Container.Args[0] | 		name = params.Container.Args[0] | ||||||
| 	} | 	} | ||||||
| 	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | 	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | ||||||
| 	defer stop() // unreachable | 	cancelContainer.Store(&stop) | ||||||
| 	z := container.New(ctx, name) | 	z := container.New(ctx, name) | ||||||
| 	z.Params = *params.Container | 	z.Params = *params.Container | ||||||
| 	z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr | 	z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr | ||||||
| 	z.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) } | 	z.WaitDelay = ShimWaitDelay | ||||||
| 	z.WaitDelay = 2 * time.Second |  | ||||||
| 
 | 
 | ||||||
| 	if err := z.Start(); err != nil { | 	if err := z.Start(); err != nil { | ||||||
| 		hlog.PrintBaseError(err, "cannot start container:") | 		hlog.PrintBaseError(err, "cannot start container:") | ||||||
|  | |||||||
| @ -128,6 +128,7 @@ in | |||||||
| 
 | 
 | ||||||
|                     container = { |                     container = { | ||||||
|                       inherit (app) |                       inherit (app) | ||||||
|  |                         immediate_termination | ||||||
|                         devel |                         devel | ||||||
|                         userns |                         userns | ||||||
|                         net |                         net | ||||||
|  | |||||||
| @ -195,6 +195,7 @@ in | |||||||
|                 ''; |                 ''; | ||||||
|               }; |               }; | ||||||
| 
 | 
 | ||||||
|  |               immediate_termination = mkEnableOption "immediate termination of the container on interrupt"; | ||||||
|               devel = mkEnableOption "debugging-related kernel interfaces"; |               devel = mkEnableOption "debugging-related kernel interfaces"; | ||||||
|               userns = mkEnableOption "user namespace creation"; |               userns = mkEnableOption "user namespace creation"; | ||||||
|               tty = mkEnableOption "access to the controlling terminal"; |               tty = mkEnableOption "access to the controlling terminal"; | ||||||
|  | |||||||
| @ -127,6 +127,21 @@ | |||||||
|         }; |         }; | ||||||
|       }; |       }; | ||||||
| 
 | 
 | ||||||
|  |       "cat.gensokyo.extern.foot.noEnablements.immediate" = { | ||||||
|  |         name = "ne-foot-immediate"; | ||||||
|  |         identity = 1; | ||||||
|  |         shareUid = true; | ||||||
|  |         verbose = true; | ||||||
|  |         immediate_termination = true; | ||||||
|  |         share = pkgs.foot; | ||||||
|  |         packages = [ ]; | ||||||
|  |         command = "foot"; | ||||||
|  |         capability = { | ||||||
|  |           dbus = false; | ||||||
|  |           pulse = false; | ||||||
|  |         }; | ||||||
|  |       }; | ||||||
|  | 
 | ||||||
|       "cat.gensokyo.extern.foot.pulseaudio" = { |       "cat.gensokyo.extern.foot.pulseaudio" = { | ||||||
|         name = "pa-foot"; |         name = "pa-foot"; | ||||||
|         identity = 2; |         identity = 2; | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								test/test.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								test/test.py
									
									
									
									
									
								
							| @ -178,6 +178,16 @@ machine.succeed("pkill -INT -f 'hakurei -v app '") | |||||||
| machine.wait_until_fails("pgrep foot", timeout=5) | machine.wait_until_fails("pgrep foot", timeout=5) | ||||||
| machine.wait_for_file("/tmp/monitor-exit-code") | machine.wait_for_file("/tmp/monitor-exit-code") | ||||||
| interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) | interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) | ||||||
|  | if interrupt_exit_code != 230: | ||||||
|  |     raise Exception(f"unexpected exit code {interrupt_exit_code}") | ||||||
|  | 
 | ||||||
|  | # Check interrupt shim behaviour immediate termination: | ||||||
|  | swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'") | ||||||
|  | wait_for_window(f"u0_a{aid(0)}@machine") | ||||||
|  | machine.succeed("pkill -INT -f 'hakurei -v app '") | ||||||
|  | machine.wait_until_fails("pgrep foot", timeout=5) | ||||||
|  | machine.wait_for_file("/tmp/monitor-exit-code") | ||||||
|  | interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code")) | ||||||
| if interrupt_exit_code != 254: | if interrupt_exit_code != 254: | ||||||
|     raise Exception(f"unexpected exit code {interrupt_exit_code}") |     raise Exception(f"unexpected exit code {interrupt_exit_code}") | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user