Jämför commits
	
		
			15 Incheckningar
		
	
	
		
			fa06287b13
			...
			215032f32c
		
	
	| Upphovsman | SHA1 | Datum | |
|---|---|---|---|
| 215032f32c | |||
| 411e00715d | |||
| 8a9b1730de | |||
| b2b501d97e | |||
| 26f391f736 | |||
| 68fd1a0a87 | |||
| b0d0beab22 | |||
| c44ce85628 | |||
| 25d8ed9bca | |||
| e389140436 | |||
| f6af1bb78f | |||
| 9192ec3aa4 | |||
| 51844ea3ab | |||
| 66c6d28dcd | |||
| 5cd45c09b7 | 
| @@ -4,6 +4,8 @@ | ||||
| ## Status | ||||
| No release yet, expiremental, API is unstable, builds will break on updates of this library.  | ||||
|  | ||||
| Currently, it's mainly evolving according to the needs of my other projects.  | ||||
|  | ||||
| ## Features | ||||
|  | ||||
|   - Systemcall filtering (using seccomp-bpf) | ||||
| @@ -36,7 +38,7 @@ the library may check against that. Execute | ||||
| `echo 1 > /proc/sys/kernel/unprivileged_userns_clone` to disable that patch for now. | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
|   - looqs: https://gitea.quitesimple.org/crtxcr/looqs | ||||
|   - qswiki: https://gitea.quitesimple.org/crtxcr/qswiki | ||||
|   - cgit sandboxed: https://gitea.quitesimple.org/crtxcr/cgitsb | ||||
|   - qpdfviewsb sandboxed (quick and dirty): https://gitea.quitesimple.org/crtxcr/qpdfviewsb | ||||
|   | ||||
							
								
								
									
										344
									
								
								qssb.h
									
									
									
									
									
								
							
							
						
						
									
										344
									
								
								qssb.h
									
									
									
									
									
								
							| @@ -40,6 +40,7 @@ | ||||
| #include <linux/filter.h> | ||||
| #include <linux/seccomp.h> | ||||
| #include <linux/version.h> | ||||
| #include <linux/audit.h> | ||||
| #include <sys/capability.h> | ||||
| #include <stddef.h> | ||||
| #include <inttypes.h> | ||||
| @@ -58,17 +59,18 @@ | ||||
| 	#endif | ||||
| #endif | ||||
|  | ||||
| //TODO: stolen from kernel samples/seccomp, GPLv2...? | ||||
| #define ALLOW \ | ||||
| 	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) | ||||
| #define DENY \ | ||||
| 	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) | ||||
|  | ||||
| #if defined(__i386__) | ||||
| #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_I386 | ||||
| #elif defined(__x86_64__) | ||||
| #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64 | ||||
| #else | ||||
| #warning Seccomp support has not been tested for qssb.h for this platform yet | ||||
| #endif | ||||
|  | ||||
| #define SYSCALL(nr, jt) \ | ||||
| 	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (nr), 0, 1), jt | ||||
|  | ||||
| #define LOAD_SYSCALL_NR \ | ||||
| 	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \ | ||||
| 		 offsetof(struct seccomp_data, nr)) | ||||
|  | ||||
| #define QSSB_UNSHARE_NETWORK 1<<1 | ||||
| #define QSSB_UNSHARE_USER 1<<2 | ||||
| @@ -140,7 +142,7 @@ static inline int landlock_restrict_self(const int ruleset_fd, | ||||
|  */ | ||||
|  /* TODO: more execv* in some architectures */ | ||||
|  /* TODO: add more */ | ||||
| static int default_blacklisted_syscals[] = { | ||||
| static long default_blacklisted_syscalls[] = { | ||||
| 	QSSB_SYS(setuid), | ||||
| 	QSSB_SYS(setgid), | ||||
| 	QSSB_SYS(chroot), | ||||
| @@ -164,7 +166,7 @@ static int default_blacklisted_syscals[] = { | ||||
|  * | ||||
|  * However, we use it to enhance "no_fs" policy, which does not solely rely | ||||
|  * on seccomp anyway */ | ||||
| static int fs_access_syscalls[] = { | ||||
| static long fs_access_syscalls[] = { | ||||
| 	QSSB_SYS(chdir), | ||||
| 	QSSB_SYS(truncate), | ||||
| 	QSSB_SYS(stat), | ||||
| @@ -188,13 +190,29 @@ struct qssb_path_policy | ||||
| 	struct qssb_path_policy *next; | ||||
| }; | ||||
|  | ||||
|  | ||||
| struct qssb_allocated_entry | ||||
| { | ||||
| 	void *data; /* the actual data */ | ||||
| 	size_t size; /* number of bytes allocated for size */ | ||||
| 	size_t size; /* number of bytes allocated for data */ | ||||
| 	size_t used; /* number of bytes in use */ | ||||
| }; | ||||
|  | ||||
| /* Special value */ | ||||
| #define QSSB_SYSCALL_MATCH_ALL -1 | ||||
|  | ||||
| #define QSSB_SYSCALL_ALLOW 1 | ||||
| #define QSSB_SYSCALL_DENY_KILL_PROCESS 2 | ||||
| #define QSSB_SYSCALL_DENY_RET_ERROR 3 | ||||
|  | ||||
|  | ||||
| struct qssb_syscall_policy | ||||
| { | ||||
| 	struct qssb_allocated_entry syscall; | ||||
| 	unsigned int policy; | ||||
| 	struct qssb_syscall_policy *next; | ||||
| }; | ||||
|  | ||||
| /* Number of bytes to grow the buffer in qssb_allocated_entry  with */ | ||||
| #define QSSB_ENTRY_ALLOC_SIZE 32 | ||||
|  | ||||
| @@ -209,6 +227,7 @@ struct qssb_policy | ||||
| 	int no_fs; | ||||
| 	int no_new_fds; | ||||
| 	int namespace_options; | ||||
| 	int disable_syscall_filter; | ||||
| 	/* Bind mounts all paths in path_policies into the chroot and applies | ||||
| 	 non-landlock policies */ | ||||
| 	int mount_path_policies_to_chroot; | ||||
| @@ -219,9 +238,9 @@ struct qssb_policy | ||||
| 	struct qssb_path_policy *path_policies; | ||||
| 	struct qssb_path_policy **path_policies_tail; | ||||
|  | ||||
| 	/* Do not manually add entries here, use qssb_append_denied_syscall() etc. */ | ||||
| 	struct qssb_allocated_entry denied_syscalls; | ||||
| 	struct qssb_allocated_entry allowed_syscalls; | ||||
| 	/* Do not manually add policies here, use qssb_append_syscall_policy() */ | ||||
| 	struct qssb_syscall_policy *syscall_policies; | ||||
| 	struct qssb_syscall_policy **syscall_policies_tail; | ||||
|  | ||||
| }; | ||||
|  | ||||
| @@ -232,6 +251,11 @@ static int qssb_entry_append(struct qssb_allocated_entry *entry, void *data, siz | ||||
| 	{ | ||||
| 		size_t expandval = QSSB_ENTRY_ALLOC_SIZE > bytes ? QSSB_ENTRY_ALLOC_SIZE : bytes; | ||||
| 		size_t sizenew = entry->size + expandval; | ||||
| 		if(sizenew < entry->size) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("overflow in qssb_entry_append\n"); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 		int *datanew = (int *) realloc(entry->data, sizenew); | ||||
| 		if(datanew == NULL) | ||||
| 		{ | ||||
| @@ -247,32 +271,68 @@ static int qssb_entry_append(struct qssb_allocated_entry *entry, void *data, siz | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int qssb_append_syscall(struct qssb_allocated_entry *entry, int *syscalls, size_t n) | ||||
| static int qssb_append_syscall(struct qssb_allocated_entry *entry, long *syscalls, size_t n) | ||||
| { | ||||
| 	return qssb_entry_append(entry, syscalls, n * sizeof(int)); | ||||
| 	return qssb_entry_append(entry, syscalls, n * sizeof(long)); | ||||
| } | ||||
|  | ||||
|  | ||||
| int qssb_append_denied_syscall(struct qssb_policy *qssb_policy, int syscall) | ||||
| static int is_valid_syscall_policy(unsigned int policy) | ||||
| { | ||||
| 	return qssb_append_syscall(&qssb_policy->denied_syscalls, &syscall, 1); | ||||
| 	return policy == QSSB_SYSCALL_ALLOW || policy == QSSB_SYSCALL_DENY_RET_ERROR || policy == QSSB_SYSCALL_DENY_KILL_PROCESS; | ||||
| } | ||||
|  | ||||
| int qssb_append_allowed_syscall(struct qssb_policy *qssb_policy, int syscall) | ||||
| static void get_syscall_array(struct qssb_syscall_policy *policy, long **syscall, size_t *n) | ||||
| { | ||||
| 	return qssb_append_syscall(&qssb_policy->allowed_syscalls, &syscall, 1); | ||||
| 	*syscall = (long *) policy->syscall.data; | ||||
| 	*n = policy->syscall.used / sizeof(long); | ||||
| } | ||||
|  | ||||
| int qssb_append_allowed_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n) | ||||
| int qssb_append_syscalls_policy(struct qssb_policy *qssb_policy, unsigned int syscall_policy, long *syscalls, size_t n) | ||||
| { | ||||
| 	/* Check whether we already have this policy. If so, merge new entries to the existing ones */ | ||||
| 	struct qssb_syscall_policy *current_policy = qssb_policy->syscall_policies; | ||||
| 	while(current_policy) | ||||
| 	{ | ||||
| 		if(current_policy->policy == syscall_policy) | ||||
| 		{ | ||||
| 			return qssb_append_syscall(¤t_policy->syscall, syscalls, n); | ||||
| 		} | ||||
| 		current_policy = current_policy->next; | ||||
| 	} | ||||
|  | ||||
| 	return qssb_append_syscall(&qssb_policy->allowed_syscalls, syscalls, n); | ||||
| 	/* We don't so we create a new policy */ | ||||
| 	struct qssb_syscall_policy *newpolicy = (struct qssb_syscall_policy *) calloc(1, sizeof(struct qssb_syscall_policy)); | ||||
| 	if(newpolicy == NULL) | ||||
| 	{ | ||||
| 		QSSB_LOG_ERROR("Failed to allocate memory for syscall policy\n"); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	int ret = qssb_append_syscall(&newpolicy->syscall, syscalls, n); | ||||
| 	if(ret != 0) | ||||
| 	{ | ||||
| 		QSSB_LOG_ERROR("Failed to append syscall\n"); | ||||
| 		return -1; | ||||
| 	} | ||||
|  | ||||
| 	newpolicy->next = NULL; | ||||
| 	newpolicy->policy = syscall_policy; | ||||
|  | ||||
| 	*(qssb_policy->syscall_policies_tail) = newpolicy; | ||||
| 	qssb_policy->syscall_policies_tail = &(newpolicy->next); | ||||
|  | ||||
| 	qssb_policy->disable_syscall_filter = 0; | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int qssb_append_denied_syscalls(struct qssb_policy *qssb_policy, int *syscalls, size_t n) | ||||
| int qssb_append_syscall_policy(struct qssb_policy *qssb_policy, unsigned int syscall_policy, long syscall) | ||||
| { | ||||
| 	return qssb_append_syscalls_policy(qssb_policy, syscall_policy, &syscall, 1); | ||||
| } | ||||
|  | ||||
| 	return qssb_append_syscall(&qssb_policy->denied_syscalls, syscalls, n); | ||||
| int qssb_append_syscall_default_policy(struct qssb_policy *qssb_policy, unsigned int default_policy) | ||||
| { | ||||
| 	return qssb_append_syscall_policy(qssb_policy, default_policy, QSSB_SYSCALL_MATCH_ALL); | ||||
| } | ||||
|  | ||||
| /* Creates the default policy | ||||
| @@ -287,25 +347,16 @@ struct qssb_policy *qssb_init_policy() | ||||
| 	result->no_fs = 0; | ||||
| 	result->no_new_fds = 0; | ||||
| 	result->namespace_options = QSSB_UNSHARE_MOUNT | QSSB_UNSHARE_USER; | ||||
| 	result->disable_syscall_filter = 0; | ||||
| 	result->chdir_path = NULL; | ||||
| 	result->mount_path_policies_to_chroot = 0; | ||||
| 	result->chroot_target_path[0] = '\0'; | ||||
| 	result->path_policies = NULL; | ||||
| 	result->path_policies_tail = &(result->path_policies); | ||||
| 	result->allowed_syscalls.data = NULL; | ||||
| 	result->allowed_syscalls.size = 0; | ||||
| 	result->allowed_syscalls.used = 0; | ||||
| 	result->denied_syscalls.data = NULL; | ||||
| 	result->denied_syscalls.size = 0; | ||||
| 	result->denied_syscalls.used = 0; | ||||
|  | ||||
| 	size_t blacklisted_syscalls_count = sizeof(default_blacklisted_syscals)/sizeof(default_blacklisted_syscals[0]); | ||||
| 	result->syscall_policies = NULL; | ||||
| 	result->syscall_policies_tail = &(result->syscall_policies); | ||||
|  | ||||
| 	int appendresult = qssb_append_denied_syscalls(result, default_blacklisted_syscals, blacklisted_syscalls_count); | ||||
| 	if(appendresult != 0) | ||||
| 	{ | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| @@ -350,6 +401,8 @@ int qssb_append_path_policy(struct qssb_policy *qssb_policy, unsigned int path_p | ||||
| 	return qssb_append_path_policies(qssb_policy, path_policy, path, NULL); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Fills buffer with random characters a-z. | ||||
|  * The string will be null terminated. | ||||
| @@ -532,6 +585,14 @@ void qssb_free_policy(struct qssb_policy *ctxt) | ||||
| 			current = current->next; | ||||
| 			free(tmp); | ||||
| 		} | ||||
|  | ||||
| 		struct qssb_syscall_policy *sc_policy = ctxt->syscall_policies; | ||||
| 		while(sc_policy != NULL) | ||||
| 		{ | ||||
| 			struct qssb_syscall_policy *tmp = sc_policy; | ||||
| 			sc_policy = sc_policy->next; | ||||
| 			free(tmp); | ||||
| 		} | ||||
| 		free(ctxt); | ||||
| 	} | ||||
| } | ||||
| @@ -626,36 +687,72 @@ static int drop_caps() | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| static void append_syscalls_to_bpf(long *syscalls, size_t n, unsigned int action, struct sock_filter *filter, unsigned short int *start_index) | ||||
| { | ||||
| 	if(action == QSSB_SYSCALL_ALLOW) | ||||
| 	{ | ||||
| 		action = SECCOMP_RET_ALLOW; | ||||
| 	} | ||||
| 	if(action == QSSB_SYSCALL_DENY_KILL_PROCESS) | ||||
| 	{ | ||||
| 		action = SECCOMP_RET_KILL_PROCESS; | ||||
| 	} | ||||
| 	if(action == QSSB_SYSCALL_DENY_RET_ERROR) | ||||
| 	{ | ||||
| 		action = SECCOMP_RET_ERRNO|EACCES; | ||||
| 	} | ||||
| 	for(size_t i = 0; i < n; i++) | ||||
| 	{ | ||||
| 		long syscall = syscalls[i]; | ||||
| 		if(syscall != QSSB_SYSCALL_MATCH_ALL) | ||||
| 		{ | ||||
| 			struct sock_filter syscall_check = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, syscall, 0, 1); | ||||
| 			filter[(*start_index)++] = syscall_check; | ||||
| 		} | ||||
| 		struct sock_filter syscall_action = BPF_STMT(BPF_RET+BPF_K, action); | ||||
| 		/* TODO: we can do better than adding this below every jump */ | ||||
| 		filter[(*start_index)++] = syscall_action; | ||||
| 	} | ||||
| } | ||||
| /* | ||||
|  * Enables the per_syscall seccomp action for system calls | ||||
|  * Enables the seccomp policy | ||||
|  * | ||||
|  * syscalls: array of system calls numbers. | ||||
|  * per_syscall: action to apply for each system call | ||||
|  * default_action: the default action at the end | ||||
|  * policy: qssb policy object | ||||
|  * | ||||
|  * @returns: 0 on success, -1 on error | ||||
|  */ | ||||
| static int seccomp_enable(int *syscalls, size_t n, unsigned int per_syscall, unsigned int default_action) | ||||
|  | ||||
| static int qssb_enable_syscall_policy(struct qssb_policy *policy) | ||||
| { | ||||
| 	struct sock_filter filter[1024] = | ||||
| 	{ | ||||
| 		LOAD_SYSCALL_NR, | ||||
| 		BPF_STMT(BPF_LD+BPF_W+BPF_ABS,offsetof(struct seccomp_data, arch)), | ||||
| 		BPF_JUMP (BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0), | ||||
| 		BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), | ||||
| 		BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), | ||||
| 		BPF_JUMP(BPF_JMP+BPF_JGE+BPF_K, __X32_SYSCALL_BIT, 0, 1), | ||||
| 		BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS), | ||||
| 	}; | ||||
|  | ||||
| 	unsigned short int current_filter_index = 1; | ||||
| 	for(size_t i = 0; i < n; i++) | ||||
| 	unsigned short int current_filter_index = 6; | ||||
|  | ||||
| 	struct qssb_syscall_policy *current_policy = policy->syscall_policies; | ||||
| 	while(current_policy) | ||||
| 	{ | ||||
| 		unsigned int sysc = (unsigned int) syscalls[i]; | ||||
| 		struct sock_filter syscall = BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, sysc, 0, 1); | ||||
| 		struct sock_filter action = BPF_STMT(BPF_RET+BPF_K, per_syscall); | ||||
| 		filter[current_filter_index++] = syscall; | ||||
| 		filter[current_filter_index++] = action; | ||||
| 		if(!is_valid_syscall_policy(current_policy->policy)) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("invalid syscall policy specified"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 		long *syscalls = NULL; | ||||
| 		size_t n = 0; | ||||
| 		get_syscall_array(current_policy, &syscalls, &n); | ||||
| 		append_syscalls_to_bpf(syscalls, n, current_policy->policy, filter, ¤t_filter_index); | ||||
| 		current_policy = current_policy->next; | ||||
| 	} | ||||
|  | ||||
| 	struct sock_filter da = BPF_STMT(BPF_RET+BPF_K, default_action); | ||||
| 	filter[current_filter_index] = da; | ||||
|  | ||||
| 	++current_filter_index; | ||||
| 	struct sock_fprog prog = { | ||||
| 		.len = current_filter_index , | ||||
| 		.filter = filter, | ||||
| @@ -670,26 +767,6 @@ static int seccomp_enable(int *syscalls, size_t n, unsigned int per_syscall, uns | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Blacklists the specified systemcalls. | ||||
|  * | ||||
|  * syscalls: array of system calls numbers. | ||||
|  */ | ||||
| static int seccomp_enable_blacklist(int *syscalls, size_t n) | ||||
| { | ||||
| 	return seccomp_enable(syscalls, n, SECCOMP_RET_KILL, SECCOMP_RET_ALLOW); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Whitelists the specified systemcalls. | ||||
|  * | ||||
|  * syscalls: array of system calls numbers. | ||||
|  */ | ||||
| static int seccomp_enable_whitelist(int *syscalls, size_t n) | ||||
| { | ||||
| 	return seccomp_enable(syscalls, n, SECCOMP_RET_ALLOW, SECCOMP_RET_KILL); | ||||
| } | ||||
|  | ||||
| #if HAVE_LANDLOCK == 1 | ||||
| static unsigned int qssb_flags_to_landlock(unsigned int flags) | ||||
| { | ||||
| @@ -809,12 +886,17 @@ static int landlock_prepare_ruleset(struct qssb_path_policy *policies) | ||||
| /* Checks for illogical or dangerous combinations */ | ||||
| static int check_policy_sanity(struct qssb_policy *policy) | ||||
| { | ||||
| 	if(policy->denied_syscalls.used > 0 && policy->allowed_syscalls.used > 0) | ||||
| 	if(policy->no_new_privs != 1) | ||||
| 	{ | ||||
| 		QSSB_LOG_ERROR("Error: Cannot mix allowed and denied systemcalls in policy\n"); | ||||
| 		return -EINVAL; | ||||
| 		if(policy->syscall_policies != NULL) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/* TODO: check if we have ALLOWED, but no default deny */ | ||||
|  | ||||
| 	if(policy->mount_path_policies_to_chroot == 1) | ||||
| 	{ | ||||
| 		if(policy->path_policies == NULL) | ||||
| @@ -829,14 +911,6 @@ static int check_policy_sanity(struct qssb_policy *policy) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(policy->no_new_privs != 1) | ||||
| 	{ | ||||
| 		if(policy->allowed_syscalls.used > 0 || policy->denied_syscalls.used > 0) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("no_new_privs = 1 is required for seccomp filtering!\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(policy->path_policies != NULL) | ||||
| 	{ | ||||
| @@ -854,6 +928,45 @@ static int check_policy_sanity(struct qssb_policy *policy) | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	struct qssb_syscall_policy *syscall_policy = policy->syscall_policies; | ||||
| 	if(syscall_policy != NULL) | ||||
| 	{ | ||||
| 		/* A few sanitiy checks... but we cannot check overall whether it's reasonable */ | ||||
| 		int i = 0; | ||||
| 		int last_match_all = -1; | ||||
| 		int match_all_policy = 0; | ||||
| 		int last_policy; | ||||
| 		while(syscall_policy) | ||||
| 		{ | ||||
| 			long *syscall; | ||||
| 			size_t n = 0; | ||||
| 			get_syscall_array(syscall_policy, &syscall, &n); | ||||
| 			if(syscall[n-1] == QSSB_SYSCALL_MATCH_ALL) | ||||
| 			{ | ||||
| 				last_match_all = i; | ||||
| 				match_all_policy = syscall_policy->policy; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				last_policy = syscall_policy->policy; | ||||
| 			} | ||||
| 			syscall_policy = syscall_policy->next; | ||||
| 			++i; | ||||
| 		} | ||||
| 		if(last_match_all == -1 || i - last_match_all != 1) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("The last entry in the syscall policy list must match all syscalls (default rule)\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 		/* Most likely a mistake and not intended */ | ||||
| 		if(last_policy == match_all_policy) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("Last policy for a syscall matches default policy\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| @@ -897,20 +1010,39 @@ static int enable_no_fs(struct qssb_policy *policy) | ||||
| 			return -1; | ||||
| 		} | ||||
|  | ||||
| 		if(policy->allowed_syscalls.used == 0) | ||||
| 		//TODO: we don't have to do this if there whitelisted policies, in that case we will be behind the default deny anyway | ||||
| 		size_t fs_access_syscalls_count = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]); | ||||
| 		int ret = qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, fs_access_syscalls, fs_access_syscalls_count); | ||||
| 		if(ret != 0) | ||||
| 		{ | ||||
| 			size_t fs_access_syscalls_count = sizeof(fs_access_syscalls)/sizeof(fs_access_syscalls[0]); | ||||
|  | ||||
| 			int ret = qssb_append_denied_syscalls(policy, fs_access_syscalls, fs_access_syscalls_count); | ||||
| 			if(ret != 0) | ||||
| 			{ | ||||
| 				QSSB_LOG_ERROR("Failed to add system calls to blacklist\n"); | ||||
| 				return -1; | ||||
| 			} | ||||
| 			QSSB_LOG_ERROR("Failed to add system calls to policy\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 		if(qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW) != 0) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("Failed to add default policy when adding denied filesystem-related system calls\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 		return 0; | ||||
| } | ||||
|  | ||||
| static int qssb_append_predefined_standard_syscall_policy(struct qssb_policy *policy) | ||||
| { | ||||
| 	size_t blacklisted_syscalls_count = sizeof(default_blacklisted_syscalls)/sizeof(default_blacklisted_syscalls[0]); | ||||
|  | ||||
| 	int appendresult = qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, default_blacklisted_syscalls, blacklisted_syscalls_count); | ||||
| 	if(appendresult != 0) | ||||
| 	{ | ||||
| 		return 1; | ||||
| 	} | ||||
| 	appendresult = qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW); | ||||
| 	if(appendresult != 0) | ||||
| 	{ | ||||
| 		return 1; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| /* Enables the specified qssb_policy. | ||||
|  * | ||||
|  * This function is not atomic (and can't be). This means some | ||||
| @@ -1058,26 +1190,18 @@ int qssb_enable_policy(struct qssb_policy *policy) | ||||
| 	close(landlock_ruleset_fd); | ||||
| #endif | ||||
|  | ||||
| 	if(policy->allowed_syscalls.used > 0) | ||||
| 	if(policy->syscall_policies == NULL && policy->disable_syscall_filter == 0) | ||||
| 	{ | ||||
| 		int *syscalls = (int *)policy->allowed_syscalls.data; | ||||
| 		size_t n = policy->allowed_syscalls.used / sizeof(int); | ||||
| 		if(seccomp_enable_whitelist(syscalls, n) < 0) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("seccomp_enable_whitelist failed\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 			if(qssb_append_predefined_standard_syscall_policy(policy) != 0) | ||||
| 			{ | ||||
| 				QSSB_LOG_ERROR("Failed to add standard predefined syscall policy\n"); | ||||
| 				return -1; | ||||
| 			} | ||||
| 	} | ||||
|  | ||||
| 	if(policy->denied_syscalls.used > 0) | ||||
| 	if(policy->syscall_policies != NULL) | ||||
| 	{ | ||||
| 		int *syscalls = (int *)policy->denied_syscalls.data; | ||||
| 		size_t n = policy->denied_syscalls.used / sizeof(int); | ||||
| 		if(seccomp_enable_blacklist(syscalls, n) < 0) | ||||
| 		{ | ||||
| 			QSSB_LOG_ERROR("seccomp_enable_blacklist failed\n"); | ||||
| 			return -1; | ||||
| 		} | ||||
| 		return qssb_enable_syscall_policy(policy); | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
|   | ||||
							
								
								
									
										217
									
								
								test.c
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								test.c
									
									
									
									
									
								
							| @@ -4,47 +4,113 @@ | ||||
| #include <dirent.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/wait.h> | ||||
|  | ||||
| int test_default_main(int argc, char *argv[]) | ||||
| int xqssb_enable_policy(struct qssb_policy *policy) | ||||
| { | ||||
| 	int ret = qssb_enable_policy(policy); | ||||
| 	if(ret != 0) | ||||
| 	{ | ||||
| 		fprintf(stderr, "qssb_enable_policy() failed: %i\n", ret); | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int test_default_main() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	int ret = qssb_enable_policy(policy); | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| int test_both_syscalls(int argc, char *argv[]) | ||||
| static int test_expected_kill(int (*f)()) | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	int syscalls[] = {1,2,3}; | ||||
|  | ||||
| 	qssb_append_denied_syscalls(policy, syscalls, 3); | ||||
| 	qssb_append_allowed_syscalls(policy, syscalls, 3); | ||||
|  | ||||
| 	int ret = qssb_enable_policy(policy); | ||||
| 	if(ret != 0) | ||||
| 	pid_t pid = fork(); | ||||
| 	if(pid == 0) | ||||
| 	{ | ||||
| 		return 0; | ||||
| 		return f(); | ||||
| 	} | ||||
| 	return 1; | ||||
| } | ||||
| 	int status = 0; | ||||
| 	waitpid(pid, &status, 0); | ||||
|  | ||||
| int test_seccomp_blacklisted(int argc, char *argv[]) | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
|  | ||||
| 	qssb_append_denied_syscall(policy, QSSB_SYS(getuid)); | ||||
|  | ||||
| 	int ret = qssb_enable_policy(policy); | ||||
| 	uid_t pid = geteuid(); | ||||
| 	pid = getuid(); | ||||
| 	if(WIFSIGNALED(status)) | ||||
| 	{ | ||||
| 		int c = WTERMSIG(status); | ||||
| 		if(c == SIGSYS) | ||||
| 		{ | ||||
| 			printf("Got expected signal\n"); | ||||
| 			return 0; | ||||
| 		} | ||||
| 		printf("Unexpected status code: %i\n", c); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		int c = WEXITSTATUS(status); | ||||
| 		printf("Process was not killed, test fails. Status code of exit: %i\n", c); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int test_seccomp_blacklisted_call_permitted(int argc, char *argv[]) | ||||
|  | ||||
| static int test_successful_exit(int (*f)()) | ||||
| { | ||||
| 	pid_t pid = fork(); | ||||
| 	if(pid == 0) | ||||
| 	{ | ||||
| 		return f(); | ||||
| 	} | ||||
| 	int status = 0; | ||||
| 	waitpid(pid, &status, 0); | ||||
|  | ||||
| 	if(WIFSIGNALED(status)) | ||||
| 	{ | ||||
| 		int c = WTERMSIG(status); | ||||
| 		printf("Received signal, which was not expected. Signal was: %i\n", c); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		int c = WEXITSTATUS(status); | ||||
| 		if(c != 0) | ||||
| 		{ | ||||
| 			printf("Process failed to exit properly. Status code is: %i\n", c); | ||||
| 		} | ||||
| 		return c; | ||||
| 	} | ||||
| 	printf("Process exited sucessfully as expected"); | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| static int do_test_seccomp_blacklisted() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid)); | ||||
| 	qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW); | ||||
|  | ||||
| 	xqssb_enable_policy(policy); | ||||
|  | ||||
| 	uid_t pid = geteuid(); | ||||
| 	pid = getuid(); | ||||
| 	return 0; | ||||
|  | ||||
|  | ||||
| } | ||||
| int test_seccomp_blacklisted() | ||||
| { | ||||
| 	return test_expected_kill(&do_test_seccomp_blacklisted); | ||||
| } | ||||
|  | ||||
|  | ||||
| static int do_test_seccomp_blacklisted_call_permitted() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
|  | ||||
| 	qssb_append_denied_syscall(policy, QSSB_SYS(getuid)); | ||||
| 	qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid)); | ||||
| 	qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW); | ||||
|  | ||||
| 	int ret = qssb_enable_policy(policy); | ||||
| 	//geteuid is not blacklisted, so must succeed | ||||
| @@ -52,7 +118,71 @@ int test_seccomp_blacklisted_call_permitted(int argc, char *argv[]) | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int test_landlock(int argc, char *argv[]) | ||||
|  | ||||
| int test_seccomp_blacklisted_call_permitted() | ||||
| { | ||||
| 	return test_successful_exit(&do_test_seccomp_blacklisted_call_permitted); | ||||
| } | ||||
|  | ||||
| static int do_test_seccomp_x32_kill() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
|  | ||||
| 	qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid)); | ||||
| 	qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW); | ||||
|  | ||||
| 	xqssb_enable_policy(policy); | ||||
|  | ||||
| 	/* Attempt to bypass by falling back to x32 should be blocked */ | ||||
| 	syscall(QSSB_SYS(getuid)+__X32_SYSCALL_BIT); | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| int test_seccomp_x32_kill() | ||||
| { | ||||
| 	return test_expected_kill(&do_test_seccomp_x32_kill); | ||||
| } | ||||
|  | ||||
| /* Tests whether seccomp rules end with a policy matching all syscalls */ | ||||
| int test_seccomp_require_last_matchall() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
|  | ||||
| 	qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, QSSB_SYS(getuid)); | ||||
|  | ||||
| 	int status = qssb_enable_policy(policy); | ||||
| 	if(status == 0) | ||||
| 	{ | ||||
| 		printf("Failed. Should not have been enabled!"); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static int do_test_seccomp_errno() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
|  | ||||
| 	qssb_append_syscall_policy(policy, QSSB_SYSCALL_DENY_RET_ERROR, QSSB_SYS(close)); | ||||
| 	qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW); | ||||
|  | ||||
| 	xqssb_enable_policy(policy); | ||||
| 	uid_t id = getuid(); | ||||
|  | ||||
| 	int fd = close(0); | ||||
| 	printf("close() return code: %i, errno: %s\n", fd, strerror(errno)); | ||||
| 	return fd == -1 ? 0 : 1; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| int test_seccomp_errno() | ||||
| { | ||||
| 	return test_successful_exit(&do_test_seccomp_errno); | ||||
| } | ||||
|  | ||||
| int test_landlock() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/proc/self/fd"); | ||||
| @@ -65,7 +195,7 @@ int test_landlock(int argc, char *argv[]) | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| int test_landlock_deny_write(int argc, char *argv[]) | ||||
| int test_landlock_deny_write() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ, "/tmp/"); | ||||
| @@ -78,7 +208,7 @@ int test_landlock_deny_write(int argc, char *argv[]) | ||||
| 	return 1; | ||||
| } | ||||
|  | ||||
| int test_nofs(int argc, char *argv[]) | ||||
| int test_nofs() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	policy->no_fs = 1; | ||||
| @@ -94,21 +224,21 @@ int test_nofs(int argc, char *argv[]) | ||||
| 	if(s == -1) | ||||
| 	{ | ||||
| 		fprintf(stderr, "Failed to open socket but this was not requested by policy\n"); | ||||
| 		return 0; | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	/* Expect seccomp to take care of this */ | ||||
| 	if(open("/test", O_CREAT | O_WRONLY) >= 0) | ||||
| 	{ | ||||
| 		fprintf(stderr, "Failed: Do not expect write access\n"); | ||||
| 		return -1; | ||||
| 		fprintf(stderr, "Failed: We do not expect write access\n"); | ||||
| 		return 1; | ||||
| 	} | ||||
|  | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| int test_no_new_fds(int argc, char *argv[]) | ||||
| int test_no_new_fds() | ||||
| { | ||||
| 	struct qssb_policy *policy = qssb_init_policy(); | ||||
| 	policy->no_new_fds = 1; | ||||
| @@ -140,23 +270,24 @@ int test_no_new_fds(int argc, char *argv[]) | ||||
| struct dispatcher | ||||
| { | ||||
| 	char *name; | ||||
| 	int (*f)(int, char **); | ||||
| 	bool must_exit_zero; | ||||
| 	int (*f)(); | ||||
| }; | ||||
|  | ||||
| struct dispatcher dispatchers[] = { | ||||
| 	{ "default", &test_default_main, true }, | ||||
| 	{ "seccomp-blacklisted", &test_seccomp_blacklisted, false }, | ||||
| 	{ "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted, true }, | ||||
| 	{ "landlock", &test_landlock, true }, | ||||
| 	{ "landlock-deny-write", &test_landlock_deny_write, true }, | ||||
| 	{ "no_fs", &test_nofs, false}, | ||||
| 	{ "no_new_fds", &test_no_new_fds, true} | ||||
| 	{ "default", &test_default_main }, | ||||
| 	{ "seccomp-blacklisted", &test_seccomp_blacklisted}, | ||||
| 	{ "seccomp-blacklisted-permitted", &test_seccomp_blacklisted_call_permitted}, | ||||
| 	{ "seccomp-x32-kill", &test_seccomp_x32_kill}, | ||||
| 	{ "seccomp-require-last-matchall", &test_seccomp_require_last_matchall}, | ||||
| 	{ "seccomp-errno", &test_seccomp_errno}, | ||||
| 	{ "landlock", &test_landlock}, | ||||
| 	{ "landlock-deny-write", &test_landlock_deny_write }, | ||||
| 	{ "no_fs", &test_nofs}, | ||||
| 	{ "no_new_fds", &test_no_new_fds} | ||||
| }; | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
|  | ||||
| 	if(argc < 2) | ||||
| 	{ | ||||
| 		fprintf(stderr, "Usage: %s [testname]\n", argv[0]); | ||||
| @@ -167,7 +298,7 @@ int main(int argc, char *argv[]) | ||||
| 	{ | ||||
| 		for(unsigned int i = 0; i < sizeof(dispatchers)/sizeof(dispatchers[0]); i++) | ||||
| 		{ | ||||
| 			printf("%s:%i\n", dispatchers[i].name, dispatchers[i].must_exit_zero ? 1 : 0); | ||||
| 			printf("%s\n", dispatchers[i].name); | ||||
| 		} | ||||
| 		return EXIT_SUCCESS; | ||||
| 	} | ||||
| @@ -177,7 +308,7 @@ int main(int argc, char *argv[]) | ||||
| 		struct dispatcher *current = &dispatchers[i]; | ||||
| 		if(strcmp(current->name, test) == 0) | ||||
| 		{ | ||||
| 			return current->f(argc, argv); | ||||
| 			return current->f(); | ||||
| 		} | ||||
| 	} | ||||
| 	fprintf(stderr, "Unknown test\n"); | ||||
|   | ||||
							
								
								
									
										32
									
								
								test.sh
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								test.sh
									
									
									
									
									
								
							| @@ -32,34 +32,23 @@ function runtest_success() | ||||
| function runtest() | ||||
| { | ||||
| 	testname="$1" | ||||
| 	must_exit_zero="$2" | ||||
| 	test_log_file="$3" | ||||
| 	test_log_file="$2" | ||||
|  | ||||
| 	echo "Running: $testname. Date: $(date)" > "${test_log_file}" | ||||
|  | ||||
| 	echo -n "Running $1... " | ||||
| 	#exit 1 to suppress shell message like "./test.sh: line 18: pid Bad system call" | ||||
| 	(./test $1 || exit 1) &>> "${test_log_file}" | ||||
| 	#exit $? to suppress shell message like "./test.sh: line 18: pid Bad system call" | ||||
| 	(./test $1 || exit $?) &>> "${test_log_file}" | ||||
| 	ret=$? | ||||
| 	SUCCESS=0 | ||||
| 	if [ $must_exit_zero -eq 1 ] ; then | ||||
| 		if [ $ret -eq 0 ] ; then | ||||
| 			runtest_success | ||||
| 			SUCCESS=1 | ||||
| 		else | ||||
| 			runtest_fail | ||||
| 		fi | ||||
| 	SUCCESS="no" | ||||
| 	if [ $ret -eq 0 ] ; then | ||||
| 		runtest_success | ||||
| 		SUCCESS="yes" | ||||
| 	else | ||||
| 		if [ $ret -eq 0 ] ; then | ||||
| 			runtest_fail | ||||
| 		else | ||||
| 			runtest_success | ||||
| 			SUCCESS=1 | ||||
| 		fi | ||||
| 		runtest_fail | ||||
| 	fi | ||||
|  | ||||
| 	echo "Finished: ${testname}. Date: $(date). Success: $SUCCESS" >> "${test_log_file}" | ||||
|  | ||||
| } | ||||
|  | ||||
| GIT_ID=$( git log --pretty="format:%h" -n1 ) | ||||
| @@ -73,9 +62,8 @@ LOG_OUTPUT_DIR_PATH="${LOG_OUTPUT_DIR}/qssb_test_${GIT_ID}_${TIMESTAMP}" | ||||
| [ -d "$LOG_OUTPUT_DIR_PATH" ] || mkdir -p "$LOG_OUTPUT_DIR_PATH" | ||||
|  | ||||
| for test in $( ./test --dumptests ) ; do | ||||
| 	testname=$( echo $test | cut -d":" -f1 ) | ||||
| 	must_exit_zero=$( echo "$test" | cut -d":" -f2 ) | ||||
| 	runtest "$testname" $must_exit_zero "${LOG_OUTPUT_DIR_PATH}/log.${testname}" | ||||
| 	testname=$( echo $test ) | ||||
| 	runtest "$testname" "${LOG_OUTPUT_DIR_PATH}/log.${testname}" | ||||
| done | ||||
| echo | ||||
| echo "Tests finished. Logs in $(realpath ${LOG_OUTPUT_DIR_PATH})" | ||||
|   | ||||
		Referens i nytt ärende
	
	Block a user