[LTP] [PATCH v3] add_key05: Avoid race with key garbage collection

Yang Xu xuyang2018.jy@cn.fujitsu.com
Fri Apr 10 06:00:13 CEST 2020


Hi Richard

> The key subsystem independently tracks user info against UID. If a user is
> deleted and the UID reused for a new user then the key subsystem will mistake
> the new user for the old one.
> 
> The keys/keyrings may not be accessible to the new user, but if they are not
> yet garbage collected (which happens asynchronously) then the new user may be
> exceeding its quota limits.
> 
> This results in a race condition where this test can fail because the old
> thread keyring is taking up the full quota. We can avoid this by creating
> multiple users in parallel.
> 
> This means when -i is used many users will be created. The number of new users
> is limited to 10 and after the first 10 we begin reusing them. It seems best
> to avoid creating a very large number of users as this may stress the system
> in ways that doesn't make sense for this test. There is a one second delay
> after every 10 iterations to give the system time to free keys. This won't be
> enough on some systems, but I doubt running this test with -i and expecting a
> consistent result is sane.
> 
> Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
> Acked-by: Jan Stancek <jstancek@redhat.com>
> ---
> 
> V3:
> * Remove volatile
> * Correct note on -i
> 
> testcases/kernel/syscalls/add_key/add_key05.c | 97 ++++++++++++-------
>   1 file changed, 62 insertions(+), 35 deletions(-)
> 
> diff --git a/testcases/kernel/syscalls/add_key/add_key05.c b/testcases/kernel/syscalls/add_key/add_key05.c
> index f64c359bb..e3d310788 100644
> --- a/testcases/kernel/syscalls/add_key/add_key05.c
> +++ b/testcases/kernel/syscalls/add_key/add_key05.c
> @@ -10,6 +10,10 @@
>    * This is also a regression test for
>    * commit a08bf91ce28e ("KEYS: allow reaching the keys quotas exactly")
>    * commit 2e356101e72a ("KEYS: reaching the keys quotas correctly")
> + *
> + * If you run this test with -i > 5 then expect to see some sporadic failures
> + * where add_key fails with EDQUOT. Keys are freed asynchronously and we only
> + * create up to 10 users to avoid race conditions.
>    */
>   
>   #include <stdio.h>
> @@ -18,47 +22,53 @@
>   #include "tst_test.h"
>   #include "lapi/keyctl.h"
>   
> +#define MAX_USERS 10
> +
>   static char *user_buf;
> -static const char *username = "ltp_add_key05";
> -static int user_added;
> -struct passwd *ltpuser;
> -static char fmt[1024];
> +static uid_t ltpuser[MAX_USERS];
> +
> +static unsigned int usern;
> +static unsigned int useri;
>   
>   static const char *const save_restore[] = {
> +	"?/proc/sys/kernel/keys/gc_delay",
>   	"?/proc/sys/kernel/keys/maxkeys",
>   	"?/proc/sys/kernel/keys/maxbytes",
>   	NULL,
>   };
>   
> -static void add_user(void)
> +static void add_user(char n)
>   {
> -	if (user_added)
> -		return;
> -
> +	char username[] = "ltp_add_key05_n";
>   	const char *const cmd_useradd[] = {"useradd", username, NULL};
> +	struct passwd *pw;
> +
> +	username[sizeof(username) - 2] = '0' + n;
>   
>   	SAFE_CMD(cmd_useradd, NULL, NULL);
> -	user_added = 1;
> -	ltpuser = SAFE_GETPWNAM(username);
> -	sprintf(fmt, "%5u: %%*5d %%*d/%%*d %%d/%%d %%d/%%d", ltpuser->pw_uid);
> +	pw = SAFE_GETPWNAM(username);
> +	ltpuser[(unsigned int)n] = pw->pw_uid;
> +
> +	tst_res(TINFO, "Created user %s", pw->pw_name);
>   }
>   
> -static void clean_user(void)
> +static void clean_user(char n)
>   {
> -	if (!user_added)
> -		return;
> -
> +	char username[] = "ltp_add_key05_n";
>   	const char *const cmd_userdel[] = {"userdel", "-r", username, NULL};
>   
> +	username[sizeof(username) - 2] = '0' + n;
> +
>   	if (tst_cmd(cmd_userdel, NULL, NULL, TST_CMD_PASS_RETVAL))
>   		tst_res(TWARN | TERRNO, "'userdel -r %s' failed", username);
> -	else
> -		user_added = 0;
>   }
>   
>   static inline void parse_proc_key_users(int *used_key, int *max_key, int *used_bytes, int *max_bytes)
>   {
>   	unsigned int val[4];
> +	char fmt[1024];
> +
> +	sprintf(fmt, "%5u: %%*5d %%*d/%%*d %%d/%%d %%d/%%d", ltpuser[useri]);
>   	SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &val[0], &val[1], &val[2], &val[3]);
>   
>   	if (used_key)
> @@ -121,8 +131,8 @@ static void verify_max_btyes(void)
>   	plen = max_bytes - used_bytes - delta - strlen("test_xxx") - 1;
>   	TEST(add_key("user", "test_max", buf, plen, KEY_SPEC_THREAD_KEYRING));
>   	if (TST_RET == -1) {
> -		 tst_res(TFAIL | TTERRNO, "add_key(test_max) failed unexpectedly");
> -		 return;
> +		tst_res(TFAIL | TTERRNO, "add_key(test_max) failed unexpectedly");
> +		return;
>   	}
>   
>   	tst_res(TPASS, "add_key(test_max) succeeded as expected");
> @@ -170,37 +180,54 @@ count:
>   
>   static void do_test(unsigned int n)
>   {
> -	add_user();
> -	if (!SAFE_FORK()) {
> -		SAFE_SETUID(ltpuser->pw_uid);
> -		TEST(add_key("user", "test1", user_buf, 64, KEY_SPEC_THREAD_KEYRING));
> -		if (TST_RET == -1) {
> -			tst_res(TFAIL | TTERRNO, "add key test1 failed");
> -			return;
> -		}
> -		if (n)
> -			verify_max_keys();
> -		else
> -			verify_max_btyes();
> -		exit(0);
> +	if (usern < MAX_USERS)
> +		add_user(usern++);
> +
> +	if (useri >= MAX_USERS) {
> +		sleep(1);
> +		useri = 0;
> +	}
> +
> +	if (SAFE_FORK()) {
> +		tst_reap_children();
> +		useri++;
> +		return;
> +	}
> +
It looks strange. Maybe only I think it is strange. Can we use old style?

if (!SAFE_FORK()) {
      ....
      ....
      test
      exit
}
    tst_reap_children();
    useri++;

Other than that, it looks good to me.
> +	SAFE_SETUID(ltpuser[useri]);
> +	tst_res(TINFO, "User: %d, UID: %d", useri, ltpuser[useri]);
> +	TEST(add_key("user", "test1", user_buf, 64, KEY_SPEC_THREAD_KEYRING));
> +	if (TST_RET == -1) {
> +		tst_res(TFAIL | TTERRNO, "add key test1 failed");
> +		return;
>   	}
> -	tst_reap_children();
> -	clean_user();
> +
> +	if (n)
> +		verify_max_keys();
> +	else
> +		verify_max_btyes();
It is typo(I introduced), I guess maintainer can fix this when merging 
this patch. btyes->bytes

>   }
>   
>   static void setup(void)
>   {
> +	SAFE_FILE_PRINTF("/proc/sys/kernel/keys/gc_delay", "1");
>   	SAFE_FILE_PRINTF("/proc/sys/kernel/keys/maxkeys", "200");
>   	SAFE_FILE_PRINTF("/proc/sys/kernel/keys/maxbytes", "20000");
>   }
>   
> +static void cleanup(void)
> +{
> +	while (usern--)
> +		clean_user(usern);
> +}
> +
>   static struct tst_test test = {
>   	.test = do_test,
>   	.tcnt = 2,
>   	.needs_root = 1,
>   	.forks_child = 1,
>   	.setup = setup,
> -	.cleanup = clean_user,
> +	.cleanup = cleanup,
>   	.save_restore = save_restore,
>   	.bufs = (struct tst_buffers []) {
>   		{&user_buf, .size = 64},
> 




More information about the ltp mailing list