internal/validate: relocate from app
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Test / Create distribution (push) Successful in 37s
				
			
		
			
				
	
				Test / Sandbox (push) Successful in 2m23s
				
			
		
			
				
	
				Test / Hakurei (push) Successful in 3m9s
				
			
		
			
				
	
				Test / Hpkg (push) Successful in 4m7s
				
			
		
			
				
	
				Test / Sandbox (race detector) (push) Successful in 4m11s
				
			
		
			
				
	
				Test / Hakurei (race detector) (push) Successful in 5m1s
				
			
		
			
				
	
				Test / Flake checks (push) Successful in 1m30s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 37s
				
			Test / Sandbox (push) Successful in 2m23s
				
			Test / Hakurei (push) Successful in 3m9s
				
			Test / Hpkg (push) Successful in 4m7s
				
			Test / Sandbox (race detector) (push) Successful in 4m11s
				
			Test / Hakurei (race detector) (push) Successful in 5m1s
				
			Test / Flake checks (push) Successful in 1m30s
				
			These are free of the dispatcher from internal/app. This change relocates them into their own package. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
		
							parent
							
								
									65342d588f
								
							
						
					
					
						commit
						274686d10d
					
				| @ -43,7 +43,7 @@ type outcomeState struct { | |||||||
| 	Identity int | 	Identity int | ||||||
| 	// Copied from Identity. | 	// Copied from Identity. | ||||||
| 	identity *stringPair[int] | 	identity *stringPair[int] | ||||||
| 	// Returned by [Hsu.MustIDMsg]. | 	// Returned by [Hsu.MustID]. | ||||||
| 	UserID int | 	UserID int | ||||||
| 	// Target init namespace uid resolved from UserID and identity. | 	// Target init namespace uid resolved from UserID and identity. | ||||||
| 	uid *stringPair[int] | 	uid *stringPair[int] | ||||||
|  | |||||||
| @ -1,15 +0,0 @@ | |||||||
| package app |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strings" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func deepContainsH(basepath, targpath string) (bool, error) { |  | ||||||
| 	const upper = ".." + string(filepath.Separator) |  | ||||||
| 
 |  | ||||||
| 	rel, err := filepath.Rel(basepath, targpath) |  | ||||||
| 	return err == nil && |  | ||||||
| 		rel != ".." && |  | ||||||
| 		!strings.HasPrefix(rel, upper), err |  | ||||||
| } |  | ||||||
| @ -6,6 +6,7 @@ import ( | |||||||
| 	"syscall" | 	"syscall" | ||||||
| 
 | 
 | ||||||
| 	"hakurei.app/container/fhs" | 	"hakurei.app/container/fhs" | ||||||
|  | 	"hakurei.app/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func init() { gob.Register(spAccountOp{}) } | func init() { gob.Register(spAccountOp{}) } | ||||||
| @ -21,7 +22,7 @@ func (s spAccountOp) toSystem(state *outcomeStateSys) error { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// default is applied in toContainer | 	// default is applied in toContainer | ||||||
| 	if state.Container.Username != "" && !isValidUsername(state.Container.Username) { | 	if state.Container.Username != "" && !validate.IsValidUsername(state.Container.Username) { | ||||||
| 		return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username)) | 		return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username)) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ import ( | |||||||
| 	"hakurei.app/container/fhs" | 	"hakurei.app/container/fhs" | ||||||
| 	"hakurei.app/container/seccomp" | 	"hakurei.app/container/seccomp" | ||||||
| 	"hakurei.app/hst" | 	"hakurei.app/hst" | ||||||
|  | 	"hakurei.app/internal/validate" | ||||||
| 	"hakurei.app/message" | 	"hakurei.app/message" | ||||||
| 	"hakurei.app/system" | 	"hakurei.app/system" | ||||||
| 	"hakurei.app/system/acl" | 	"hakurei.app/system/acl" | ||||||
| @ -243,7 +244,7 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error { | |||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if ok, err := deepContainsH(p[0], hidePaths[i]); err != nil { | 			if ok, err := validate.DeepContainsH(p[0], hidePaths[i]); err != nil { | ||||||
| 				return &hst.AppError{Step: "determine path hiding outcome", Err: err} | 				return &hst.AppError{Step: "determine path hiding outcome", Err: err} | ||||||
| 			} else if ok { | 			} else if ok { | ||||||
| 				hidePathMatch[i] = true | 				hidePathMatch[i] = true | ||||||
|  | |||||||
| @ -1,8 +0,0 @@ | |||||||
| package app |  | ||||||
| 
 |  | ||||||
| //#include <unistd.h> |  | ||||||
| import "C" |  | ||||||
| 
 |  | ||||||
| const _SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX |  | ||||||
| 
 |  | ||||||
| func sysconf(name C.int) int { return int(C.sysconf(name)) } |  | ||||||
| @ -1,28 +0,0 @@ | |||||||
| package app |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"strings" |  | ||||||
| 	"testing" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| func TestIsValidUsername(t *testing.T) { |  | ||||||
| 	t.Parallel() |  | ||||||
| 
 |  | ||||||
| 	t.Run("long", func(t *testing.T) { |  | ||||||
| 		if isValidUsername(strings.Repeat("a", sysconf(_SC_LOGIN_NAME_MAX))) { |  | ||||||
| 			t.Errorf("isValidUsername unexpected true") |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	t.Run("regexp", func(t *testing.T) { |  | ||||||
| 		if isValidUsername("0") { |  | ||||||
| 			t.Errorf("isValidUsername unexpected true") |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	t.Run("valid", func(t *testing.T) { |  | ||||||
| 		if !isValidUsername("alice") { |  | ||||||
| 			t.Errorf("isValidUsername unexpected false") |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
							
								
								
									
										8
									
								
								internal/validate/sysconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								internal/validate/sysconf.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | package validate | ||||||
|  | 
 | ||||||
|  | //#include <unistd.h> | ||||||
|  | import "C" | ||||||
|  | 
 | ||||||
|  | const SC_LOGIN_NAME_MAX = C._SC_LOGIN_NAME_MAX | ||||||
|  | 
 | ||||||
|  | func Sysconf(name C.int) int { return int(C.sysconf(name)) } | ||||||
| @ -1,6 +1,10 @@ | |||||||
| package app | package validate_test | ||||||
| 
 | 
 | ||||||
| import "testing" | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"hakurei.app/internal/validate" | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	_POSIX_LOGIN_NAME_MAX = 9 | 	_POSIX_LOGIN_NAME_MAX = 9 | ||||||
| @ -10,7 +14,7 @@ func TestSysconf(t *testing.T) { | |||||||
| 	t.Parallel() | 	t.Parallel() | ||||||
| 
 | 
 | ||||||
| 	t.Run("LOGIN_NAME_MAX", func(t *testing.T) { | 	t.Run("LOGIN_NAME_MAX", func(t *testing.T) { | ||||||
| 		if got := sysconf(_SC_LOGIN_NAME_MAX); got < _POSIX_LOGIN_NAME_MAX { | 		if got := validate.Sysconf(validate.SC_LOGIN_NAME_MAX); got < _POSIX_LOGIN_NAME_MAX { | ||||||
| 			t.Errorf("sysconf(_SC_LOGIN_NAME_MAX): %d < _POSIX_LOGIN_NAME_MAX", got) | 			t.Errorf("sysconf(_SC_LOGIN_NAME_MAX): %d < _POSIX_LOGIN_NAME_MAX", got) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| @ -1,12 +1,12 @@ | |||||||
| package app | package validate | ||||||
| 
 | 
 | ||||||
| import "regexp" | import "regexp" | ||||||
| 
 | 
 | ||||||
| // nameRegex is the default NAME_REGEX value from adduser. | // nameRegex is the default NAME_REGEX value from adduser. | ||||||
| var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z][a-zA-Z0-9_-]*\$?$`) | var nameRegex = regexp.MustCompilePOSIX(`^[a-zA-Z][a-zA-Z0-9_-]*\$?$`) | ||||||
| 
 | 
 | ||||||
| // isValidUsername returns whether the argument is a valid username | // IsValidUsername returns whether the argument is a valid username. | ||||||
| func isValidUsername(username string) bool { | func IsValidUsername(username string) bool { | ||||||
| 	return len(username) < sysconf(_SC_LOGIN_NAME_MAX) && | 	return len(username) < Sysconf(SC_LOGIN_NAME_MAX) && | ||||||
| 		nameRegex.MatchString(username) | 		nameRegex.MatchString(username) | ||||||
| } | } | ||||||
							
								
								
									
										30
									
								
								internal/validate/username_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								internal/validate/username_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | package validate_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"hakurei.app/internal/validate" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestIsValidUsername(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 
 | ||||||
|  | 	t.Run("long", func(t *testing.T) { | ||||||
|  | 		if validate.IsValidUsername(strings.Repeat("a", validate.Sysconf(validate.SC_LOGIN_NAME_MAX))) { | ||||||
|  | 			t.Errorf("IsValidUsername unexpected true") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("regexp", func(t *testing.T) { | ||||||
|  | 		if validate.IsValidUsername("0") { | ||||||
|  | 			t.Errorf("IsValidUsername unexpected true") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	t.Run("valid", func(t *testing.T) { | ||||||
|  | 		if !validate.IsValidUsername("alice") { | ||||||
|  | 			t.Errorf("IsValidUsername unexpected false") | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								internal/validate/validate.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								internal/validate/validate.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | // Package validate provides functions for validating string values of various types. | ||||||
|  | package validate | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // DeepContainsH returns whether basepath is equivalent to or is the parent of targpath. | ||||||
|  | // | ||||||
|  | // This is used for path hiding warning behaviour, the purpose of which is to improve | ||||||
|  | // user experience and is *not* a security feature and must not be treated as such. | ||||||
|  | func DeepContainsH(basepath, targpath string) (bool, error) { | ||||||
|  | 	const upper = ".." + string(filepath.Separator) | ||||||
|  | 
 | ||||||
|  | 	rel, err := filepath.Rel(basepath, targpath) | ||||||
|  | 	return err == nil && | ||||||
|  | 		rel != ".." && | ||||||
|  | 		!strings.HasPrefix(rel, upper), err | ||||||
|  | } | ||||||
| @ -1,7 +1,9 @@ | |||||||
| package app | package validate_test | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"hakurei.app/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestDeepContainsH(t *testing.T) { | func TestDeepContainsH(t *testing.T) { | ||||||
| @ -78,10 +80,10 @@ func TestDeepContainsH(t *testing.T) { | |||||||
| 	for _, tc := range testCases { | 	for _, tc := range testCases { | ||||||
| 		t.Run(tc.name, func(t *testing.T) { | 		t.Run(tc.name, func(t *testing.T) { | ||||||
| 			t.Parallel() | 			t.Parallel() | ||||||
| 			if got, err := deepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr { | 			if got, err := validate.DeepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr { | ||||||
| 				t.Errorf("deepContainsH() error = %v, wantErr %v", err, tc.wantErr) | 				t.Errorf("DeepContainsH: error = %v, wantErr %v", err, tc.wantErr) | ||||||
| 			} else if got != tc.want { | 			} else if got != tc.want { | ||||||
| 				t.Errorf("deepContainsH() = %v, want %v", got, tc.want) | 				t.Errorf("DeepContainsH: = %v, want %v", got, tc.want) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user