diff mbox series

syscalls/add_key05: add maxbytes/maxkeys test under unprivileged user

Message ID 1581081297-20034-1-git-send-email-xuyang2018.jy@cn.fujitsu.com
State Changes Requested
Headers show
Series syscalls/add_key05: add maxbytes/maxkeys test under unprivileged user | expand

Commit Message

Yang Xu Feb. 7, 2020, 1:14 p.m. UTC
Signed-off-by: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
---
 runtest/syscalls                              |   1 +
 testcases/kernel/syscalls/add_key/.gitignore  |   1 +
 testcases/kernel/syscalls/add_key/add_key05.c | 180 ++++++++++++++++++
 3 files changed, 182 insertions(+)
 create mode 100644 testcases/kernel/syscalls/add_key/add_key05.c

Comments

Cyril Hrubis Feb. 24, 2020, 3:31 p.m. UTC | #1
Hi!
> diff --git a/runtest/syscalls b/runtest/syscalls
> index 577a4a527..df7bbcbf1 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -18,6 +18,7 @@ add_key01 add_key01
>  add_key02 add_key02
>  add_key03 add_key03
>  add_key04 add_key04
> +add_key05 add_key05
>  
>  adjtimex01 adjtimex01
>  adjtimex02 adjtimex02
> diff --git a/testcases/kernel/syscalls/add_key/.gitignore b/testcases/kernel/syscalls/add_key/.gitignore
> index b9a04214d..f57dc2228 100644
> --- a/testcases/kernel/syscalls/add_key/.gitignore
> +++ b/testcases/kernel/syscalls/add_key/.gitignore
> @@ -2,3 +2,4 @@
>  /add_key02
>  /add_key03
>  /add_key04
> +/add_key05
> diff --git a/testcases/kernel/syscalls/add_key/add_key05.c b/testcases/kernel/syscalls/add_key/add_key05.c
> new file mode 100644
> index 000000000..0d5e9db28
> --- /dev/null
> +++ b/testcases/kernel/syscalls/add_key/add_key05.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2020 FUJITSU LIMITED. All rights reserved.
> + * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
> + *
> + *Description:
> + * Test unprivileged user can support the number of keys and the
> + * number of bytes consumed in payloads of the keys.The defalut value
> + * is 200 and 20000.
> + * This is also a regresstion test for
> + * commit a08bf91ce28e ("KEYS: allow reaching the keys quotas exact")
> + *
> + */
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <pwd.h>
> +#include "tst_test.h"
> +#include "lapi/keyctl.h"
> +
> +static char *user_buf, *user_buf1, *keyring_buf;
> +static const char *username = "ltp_add_key05";
> +static int user_added;
> +struct passwd *ltpuser;
> +static unsigned int used_bytes, max_bytes, used_key, max_key, data_len;
> +char fmt[1024];
> +int flag[2] = {1, 0};
> +
> +void add_user(void)
> +{
> +	if (user_added)
> +		return;
> +
> +	const char *const cmd_useradd[] = {"useradd", username, NULL};
> +	int rc;
> +
> +	switch ((rc = tst_run_cmd(cmd_useradd, NULL, NULL, 1))) {
> +	case 0:
> +		user_added = 1;
> +		ltpuser = SAFE_GETPWNAM(username);
> +		break;
> +	case 1:
> +	case 255:
> +		break;
> +	default:
> +		tst_brk(TBROK, "Useradd failed (%d)", rc);
> +	}
> +	sprintf(fmt, "%5u: %%*5d %%*d/%%*d %%d/%%d %%d/%%d", ltpuser->pw_uid);
> +}
> +
> +void clean_user(void)
> +{
> +	if (!user_added)
> +		return;
> +
> +	const char *const cmd_userdel[] = {"userdel", "-r", username, NULL};
> +
> +	if (tst_run_cmd(cmd_userdel, NULL, NULL, 1))
> +		tst_res(TWARN | TERRNO, "'userdel -r %s' failed", username);
> +	else
> +		user_added = 0;
> +}
> +
> +void verify_max_btyes(void)
> +{
> +	char *buf, *invalid_buf;
> +	int plen, invalid_plen;
> +
> +	tst_res(TINFO, "test max bytes under unprivileged user");
> +	invalid_plen = max_bytes - used_bytes - data_len - 8;

What is the -8 for, strlen("test_inv")?

I guess that it will be more readable if we used the strlen() here as
well.

> +	plen = invalid_plen - 1;
> +	buf = tst_alloc(plen);
> +	invalid_buf = tst_alloc(invalid_plen);
> +
> +	TEST(add_key("user", "test_inv", invalid_buf, invalid_plen, KEY_SPEC_THREAD_KEYRING));
> +	if (TST_RET != -1)
> +		tst_res(TFAIL, "add_key(test_inv) succeeded unexpectedltly");
> +	else {
> +		if (TST_ERR == EDQUOT)
> +			tst_res(TPASS | TTERRNO, "add_key(test_inv) failed as expected");
> +		else
> +			tst_res(TFAIL | TTERRNO, "add_key(test_inv) failed expected EDQUOT got");
> +	}
> +
> +	TEST(add_key("user", "test_max", buf, plen, KEY_SPEC_THREAD_KEYRING));
> +	if (TST_RET != -1) {
> +		tst_res(TPASS, "add_key(test_max) succeeded as expected");
> +		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
> +		if (used_bytes == max_bytes)
> +			tst_res(TPASS, "allow reaching the max bytes exactly");
> +		else
> +			tst_res(TFAIL, "max used bytes %u, key allow max bytes %u", used_bytes, max_bytes);
> +	} else
> +		tst_res(TFAIL | TTERRNO, "add_key(test_max) failed unexpectedly");
> +}
> +
> +void verify_max_keys(void)
> +{
> +	unsigned int i;
> +	char desc[10];
> +
> +	tst_res(TINFO, "test max keys under unprivileged user");
> +	for (i = used_key + 1; i < max_key; i++) {
> +		sprintf(desc, "abc%d", i);
> +		TEST(add_key("keyring", desc, keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
> +		if (TST_RET == -1)
> +			tst_res(TFAIL | TTERRNO, "add keyring key(%s) failed", desc);

Why we are using the "keyring" ring here instead? I doubt that it
matters for the quota, but it just seems strange.

Also we should stop the loop on a first failure, I guess that goto
count: would suffice.

> +	}
> +
> +	TEST(add_key("keyring", "abc200", keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
> +
> +	if (TST_RET == -1) {
> +		tst_res(TFAIL | TTERRNO, "add keyring key(abc200) failed");
> +		goto count;
> +	} else
> +		tst_res(TPASS, "add keyring key(abc200) succedd");

Why don't we just start the loop above at used_key? There is no point in
adding the last key here outside of the loop.

> +	TEST(add_key("keyring", "abc201", keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
> +	if (TST_RET != -1) {
> +		tst_res(TFAIL, "add keyring key(abc201) succeeded unexpectedly");
> +		goto count;

We should add a key with a different name than the previous "abc%d"
pattern here, if the upper limit was over 200 we would just replace a
key here instread which will not fail at all.

> +	} else {
> +		if (TST_ERR == EDQUOT)
> +			tst_res(TPASS | TTERRNO, "add keyring key(abc201) failed as expected over max key");
> +		else
> +			tst_res(TFAIL | TTERRNO, "add_keyring failed expected EDQUOT got");
> +	}
> +count:
> +	SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
> +	if (used_key == max_key)
> +		tst_res(TPASS, "allow reaching the max key exactly");
> +	else
> +		tst_res(TFAIL, "max used key %u, key allow max key %u", used_key, max_key);
> +}
> +
> +static void do_test(unsigned int n)
> +{
> +	add_user();
> +	int f_used_bytes = 0;
> +
> +	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_brk(TFAIL | TTERRNO, "add key test1 failed");
> +		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
> +		f_used_bytes = used_bytes;
> +
> +		TEST(add_key("user", "test2", user_buf1, 64, KEY_SPEC_THREAD_KEYRING));
> +		if (TST_RET == -1)
> +			tst_brk(TFAIL | TTERRNO, "add key test2 failed");

Do we really need to pass a different users_buf to each call?

> +		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
> +
> +		data_len = used_bytes - f_used_bytes - strlen("test1") - 1 - 64;

So this code here is used to determine how much data will consume the
key structure in kernel itself? I guess that this is useless to run in
the case of testing for maximal number of keys, right? Can we put this
code into a function something as get_kernel_key_data_size() and call it
from the verify_max_bytes() only?

I guess that the SAFE_FILE_LINES_SCANF() intializes the max_key for the
max_keys test. Having a global variables that are initalized in random
places makes the code really confusing, can we avoid that if poissible
please?

I guess that we can at least create:

parse_proc_key_users(int *used_keys, int *max_key, int *used_bytes, *int max_bytes)

function that would read the values into a local variables and copy the
results only if non-NULL pointers were passed.

That way the verify_max_keys() would call:
parse_proc_key_users(NULL, &max_keys, NULL, NULL);
and use the result for the loop that adds the keys.

> +		if (flag[n])

Huh, why not just if (n)?

> +			verify_max_btyes();
> +		else
> +			verify_max_keys();
> +		exit(0);
> +	}
> +	tst_reap_children();
> +	clean_user();

What is the reason to add and remove a user for each iteration?

Aare you cleaning the keys that way?

> +}
> +
> +static struct tst_test test = {
> +	.test = do_test,
> +	.tcnt = 2,
> +	.needs_root = 1,
> +	.forks_child = 1,
> +	.cleanup = clean_user,
> +	.bufs = (struct tst_buffers []) {
> +		{&keyring_buf, .size = 1},
> +		{&user_buf, .size = 64},
> +		{&user_buf1, .size = 64},
> +		{}
> +	},
> +	.tags = (const struct tst_tag[]) {
> +		{"linux-git", "a08bf91ce28"},
> +		{}
> +	}
> +};
> -- 
> 2.18.0
> 
> 
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp
Yang Xu Feb. 26, 2020, 10:19 a.m. UTC | #2
Hi!

> Hi!
>> diff --git a/runtest/syscalls b/runtest/syscalls
>> index 577a4a527..df7bbcbf1 100644
>> --
>> +void verify_max_btyes(void)
>> +{
>> +	char *buf, *invalid_buf;
>> +	int plen, invalid_plen;
>> +
>> +	tst_res(TINFO, "test max bytes under unprivileged user");
>> +	invalid_plen = max_bytes - used_bytes - data_len - 8;
> 
> What is the -8 for, strlen("test_inv")?
Yes.
> 
> I guess that it will be more readable if we used the strlen() here as
> well.
OK. I will use strlen.
> 
>> +	plen = invalid_plen - 1;
>> +	buf = tst_alloc(plen);
>> +	invalid_buf = tst_alloc(invalid_plen);
>> +
>> +	TEST(add_key("user", "test_inv", invalid_buf, invalid_plen, KEY_SPEC_THREAD_KEYRING));
>> +	if (TST_RET != -1)
>> +		tst_res(TFAIL, "add_key(test_inv) succeeded unexpectedltly");
>> +	else {
>> +		if (TST_ERR == EDQUOT)
>> +			tst_res(TPASS | TTERRNO, "add_key(test_inv) failed as expected");
>> +		else
>> +			tst_res(TFAIL | TTERRNO, "add_key(test_inv) failed expected EDQUOT got");
>> +	}
>> +
>> +	TEST(add_key("user", "test_max", buf, plen, KEY_SPEC_THREAD_KEYRING));
>> +	if (TST_RET != -1) {
>> +		tst_res(TPASS, "add_key(test_max) succeeded as expected");
>> +		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
>> +		if (used_bytes == max_bytes)
>> +			tst_res(TPASS, "allow reaching the max bytes exactly");
>> +		else
>> +			tst_res(TFAIL, "max used bytes %u, key allow max bytes %u", used_bytes, max_bytes);
>> +	} else
>> +		tst_res(TFAIL | TTERRNO, "add_key(test_max) failed unexpectedly");
>> +}
>> +
>> +void verify_max_keys(void)
>> +{
>> +	unsigned int i;
>> +	char desc[10];
>> +
>> +	tst_res(TINFO, "test max keys under unprivileged user");
>> +	for (i = used_key + 1; i < max_key; i++) {
>> +		sprintf(desc, "abc%d", i);
>> +		TEST(add_key("keyring", desc, keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
>> +		if (TST_RET == -1)
>> +			tst_res(TFAIL | TTERRNO, "add keyring key(%s) failed", desc);
> 
> Why we are using the "keyring" ring here instead? I doubt that it
> matters for the quota, but it just seems strange.
It doesn't matter quota, only because "keyrings" plen is 0. I will use 
"user" type for this.
  >
> Also we should stop the loop on a first failure, I guess that goto
> count: would suffice.
I ignored this before, thanks.
> 
>> +	}
>> +
>> +	TEST(add_key("keyring", "abc200", keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
>> +
>> +	if (TST_RET == -1) {
>> +		tst_res(TFAIL | TTERRNO, "add keyring key(abc200) failed");
>> +		goto count;
>> +	} else
>> +		tst_res(TPASS, "add keyring key(abc200) succedd");
> 
> Why don't we just start the loop above at used_key? There is no point in
> adding the last key here outside of the loop.
> 
Using start + 1 more clean:
abc100 >> the 100th key,
We can change code (including last key)as below:
for (i = used_key + 1; i <= max_key; i ++)

>> +	TEST(add_key("keyring", "abc201", keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
>> +	if (TST_RET != -1) {
>> +		tst_res(TFAIL, "add keyring key(abc201) succeeded unexpectedly");
>> +		goto count;
> 
> We should add a key with a different name than the previous "abc%d"
> pattern here, if the upper limit was over 200 we would just replace a
> key here instread which will not fail at all.
Do you mean to use  "invalid_num_key" to avoid upper limit over 200?

> 
>> +	} else {
>> +		if (TST_ERR == EDQUOT)
>> +			tst_res(TPASS | TTERRNO, "add keyring key(abc201) failed as expected over max key");
>> +		else
>> +			tst_res(TFAIL | TTERRNO, "add_keyring failed expected EDQUOT got");
>> +	}
>> +count:
>> +	SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
>> +	if (used_key == max_key)
>> +		tst_res(TPASS, "allow reaching the max key exactly");
>> +	else
>> +		tst_res(TFAIL, "max used key %u, key allow max key %u", used_key, max_key);
>> +}
>> +
>> +static void do_test(unsigned int n)
>> +{
>> +	add_user();
>> +	int f_used_bytes = 0;
>> +
>> +	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_brk(TFAIL | TTERRNO, "add key test1 failed");
>> +		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
>> +		f_used_bytes = used_bytes;
>> +
>> +		TEST(add_key("user", "test2", user_buf1, 64, KEY_SPEC_THREAD_KEYRING));
>> +		if (TST_RET == -1)
>> +			tst_brk(TFAIL | TTERRNO, "add key test2 failed");
> 
> Do we really need to pass a different users_buf to each call?
I have seen kernel code, payload is used to instantiate or update the 
key, I think it is no problme to use the same buf because we don't get 
it again from kernel space.
> 
>> +		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
>> +
>> +		data_len = used_bytes - f_used_bytes - strlen("test1") - 1 - 64;
> 
> So this code here is used to determine how much data will consume the
> key structure in kernel itself? 
I debug this code after adding test1 key, show in /proc/keys
add_key05.c:154: INFO: 1535d151 I--Q---    24 perm 3f030000     0     0 
keyring   _ses: 1

add_key05.c:154: INFO: 15d7df09 I--Q---     6 perm 1f3f0000     0 65534 
keyring   _uid.0: empty

add_key05.c:154: INFO: 1a300c72 I--Q---     1 perm 3f010000  1003     0 
user      test1: 64

add_key05.c:154: INFO: 2e57738c I--Q---     1 perm 3f010000  1003     0 
keyring   _tid: 1

the key num is 2 in /proc/key-users, I use this code to figure keyring 
  _tid: 1(I don't know why has it) consumed data bytes.
I guess that this is useless to run in
> the case of testing for maximal number of keys, right? Can we put this
> code into a function something as get_kernel_key_data_size() and call it
> from the verify_max_bytes() only?
Yes, it was only used in max_bytes.
> 
> I guess that the SAFE_FILE_LINES_SCANF() intializes the max_key for the
> max_keys test. Having a global variables that are initalized in random
> places makes the code really confusing, can we avoid that if poissible
> please?
Ok. I will avoid this and make code clean.
> 
> I guess that we can at least create:
> 
> parse_proc_key_users(int *used_keys, int *max_key, int *used_bytes, *int max_bytes)
> 
> function that would read the values into a local variables and copy the
> results only if non-NULL pointers were passed.
> 
> That way the verify_max_keys() would call:
> parse_proc_key_users(NULL, &max_keys, NULL, NULL);
> and use the result for the loop that adds the keys.
Sound good, I will use it.
> 
>> +		if (flag[n])
> 
> Huh, why not just if (n)?
> 
OK.
>> +			verify_max_btyes();
>> +		else
>> +			verify_max_keys();
>> +		exit(0);
>> +	}
>> +	tst_reap_children();
>> +	clean_user();
> 
> What is the reason to add and remove a user for each iteration?
> 
> Aare you cleaning the keys that way?
Yes, I planed to look for a wise (try KEYCTL_CLEAR
  or KEYCTL_INVALIDATE)way but failed. Can you give me some
suggestion?
> 
>> +}
>> +
>> +static struct tst_test test = {
>> +	.test = do_test,
>> +	.tcnt = 2,
>> +	.needs_root = 1,
>> +	.forks_child = 1,
>> +	.cleanup = clean_user,
>> +	.bufs = (struct tst_buffers []) {
>> +		{&keyring_buf, .size = 1},
>> +		{&user_buf, .size = 64},
>> +		{&user_buf1, .size = 64},
>> +		{}
>> +	},
>> +	.tags = (const struct tst_tag[]) {
>> +		{"linux-git", "a08bf91ce28"},
>> +		{}
>> +	}
>> +};
>> -- 
>> 2.18.0
>>
>>
>>
>>
>> -- 
>> Mailing list info: https://lists.linux.it/listinfo/ltp
>
Yang Xu Feb. 28, 2020, 7 a.m. UTC | #3
Hi!

> Hi!
> 
>> Hi!
>>> diff --git a/runtest/syscalls b/runtest/syscalls
>>> index 577a4a527..df7bbcbf1 100644
>>> -- 
>>> +void verify_max_btyes(void)
>>> +{
>>> +    char *buf, *invalid_buf;
>>> +    int plen, invalid_plen;
>>> +
>>> +    tst_res(TINFO, "test max bytes under unprivileged user");
>>> +    invalid_plen = max_bytes - used_bytes - data_len - 8;
>>
>> What is the -8 for, strlen("test_inv")?
> Yes.
>>
>> I guess that it will be more readable if we used the strlen() here as
>> well.
> OK. I will use strlen.
>>
>>> +    plen = invalid_plen - 1;
>>> +    buf = tst_alloc(plen);
>>> +    invalid_buf = tst_alloc(invalid_plen);
>>> +
>>> +    TEST(add_key("user", "test_inv", invalid_buf, invalid_plen, 
>>> KEY_SPEC_THREAD_KEYRING));
>>> +    if (TST_RET != -1)
>>> +        tst_res(TFAIL, "add_key(test_inv) succeeded unexpectedltly");
>>> +    else {
>>> +        if (TST_ERR == EDQUOT)
>>> +            tst_res(TPASS | TTERRNO, "add_key(test_inv) failed as 
>>> expected");
>>> +        else
>>> +            tst_res(TFAIL | TTERRNO, "add_key(test_inv) failed 
>>> expected EDQUOT got");
>>> +    }
>>> +
>>> +    TEST(add_key("user", "test_max", buf, plen, 
>>> KEY_SPEC_THREAD_KEYRING));
>>> +    if (TST_RET != -1) {
>>> +        tst_res(TPASS, "add_key(test_max) succeeded as expected");
>>> +        SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, 
>>> &max_key, &used_bytes, &max_bytes);
>>> +        if (used_bytes == max_bytes)
>>> +            tst_res(TPASS, "allow reaching the max bytes exactly");
>>> +        else
>>> +            tst_res(TFAIL, "max used bytes %u, key allow max bytes 
>>> %u", used_bytes, max_bytes);
>>> +    } else
>>> +        tst_res(TFAIL | TTERRNO, "add_key(test_max) failed 
>>> unexpectedly");
>>> +}
>>> +
>>> +void verify_max_keys(void)
>>> +{
>>> +    unsigned int i;
>>> +    char desc[10];
>>> +
>>> +    tst_res(TINFO, "test max keys under unprivileged user");
>>> +    for (i = used_key + 1; i < max_key; i++) {
>>> +        sprintf(desc, "abc%d", i);
>>> +        TEST(add_key("keyring", desc, keyring_buf, 0, 
>>> KEY_SPEC_THREAD_KEYRING));
>>> +        if (TST_RET == -1)
>>> +            tst_res(TFAIL | TTERRNO, "add keyring key(%s) failed", 
>>> desc);
>>
>> Why we are using the "keyring" ring here instead? I doubt that it
>> matters for the quota, but it just seems strange.
> It doesn't matter quota, only because "keyrings" plen is 0. I will use 
> "user" type for this.
>   >
>> Also we should stop the loop on a first failure, I guess that goto
>> count: would suffice.
> I ignored this before, thanks.
>>
>>> +    }
>>> +
>>> +    TEST(add_key("keyring", "abc200", keyring_buf, 0, 
>>> KEY_SPEC_THREAD_KEYRING));
>>> +
>>> +    if (TST_RET == -1) {
>>> +        tst_res(TFAIL | TTERRNO, "add keyring key(abc200) failed");
>>> +        goto count;
>>> +    } else
>>> +        tst_res(TPASS, "add keyring key(abc200) succedd");
>>
>> Why don't we just start the loop above at used_key? There is no point in
>> adding the last key here outside of the loop.
>>
> Using start + 1 more clean:
> abc100 >> the 100th key,
> We can change code (including last key)as below:
> for (i = used_key + 1; i <= max_key; i ++)
> 
>>> +    TEST(add_key("keyring", "abc201", keyring_buf, 0, 
>>> KEY_SPEC_THREAD_KEYRING));
>>> +    if (TST_RET != -1) {
>>> +        tst_res(TFAIL, "add keyring key(abc201) succeeded 
>>> unexpectedly");
>>> +        goto count;
>>
>> We should add a key with a different name than the previous "abc%d"
>> pattern here, if the upper limit was over 200 we would just replace a
>> key here instread which will not fail at all.
> Do you mean to use  "invalid_num_key" to avoid upper limit over 200?
> 
>>
>>> +    } else {
>>> +        if (TST_ERR == EDQUOT)
>>> +            tst_res(TPASS | TTERRNO, "add keyring key(abc201) failed 
>>> as expected over max key");
>>> +        else
>>> +            tst_res(TFAIL | TTERRNO, "add_keyring failed expected 
>>> EDQUOT got");
>>> +    }
>>> +count:
>>> +    SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, 
>>> &max_key, &used_bytes, &max_bytes);
>>> +    if (used_key == max_key)
>>> +        tst_res(TPASS, "allow reaching the max key exactly");
>>> +    else
>>> +        tst_res(TFAIL, "max used key %u, key allow max key %u", 
>>> used_key, max_key);
>>> +}
>>> +
>>> +static void do_test(unsigned int n)
>>> +{
>>> +    add_user();
>>> +    int f_used_bytes = 0;
>>> +
>>> +    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_brk(TFAIL | TTERRNO, "add key test1 failed");
>>> +        SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, 
>>> &max_key, &used_bytes, &max_bytes);
>>> +        f_used_bytes = used_bytes;
>>> +
>>> +        TEST(add_key("user", "test2", user_buf1, 64, 
>>> KEY_SPEC_THREAD_KEYRING));
>>> +        if (TST_RET == -1)
>>> +            tst_brk(TFAIL | TTERRNO, "add key test2 failed");
>>
>> Do we really need to pass a different users_buf to each call?
> I have seen kernel code, payload is used to instantiate or update the 
> key, I think it is no problme to use the same buf because we don't get 
> it again from kernel space.
>>
>>> +        SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, 
>>> &max_key, &used_bytes, &max_bytes);
>>> +
>>> +        data_len = used_bytes - f_used_bytes - strlen("test1") - 1 - 
>>> 64;
>>
>> So this code here is used to determine how much data will consume the
>> key structure in kernel itself? 

Sorry for the wrong info. This code is designed to compute delta, I have 
seen kernel code yestorday:
add_key calltrace as below:
add_key()
   key_create_or_update()
     key_alloc()
     __key_instantiate_and_link
       generic_key_instantiate
         key_payload_reserve
           ......

In key_alloc, we use type->def_datalen to alloc space, but when 
instantiating key, we using delta(quota_len -type->def_datalen) to 
adjust reserver space. So I compute this variable(delta, add_key05.c 
uses data_len) to avoid  that we can reach max bytes in key_alloc but 
got EDQOUT in key_payload_reserve.

more info:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/security/keys/core.rst#n1353

Also, using my v2 patch(send later), this case also fails on lastest 
kernel because of incomplete kernel commit.

more info:
https://patchwork.kernel.org/patch/11411507/

Best Regards
Yang Xu
> I debug this code after adding test1 key, show in /proc/keys
> add_key05.c:154: INFO: 1535d151 I--Q---    24 perm 3f030000     0     0 
> keyring   _ses: 1
> 
> add_key05.c:154: INFO: 15d7df09 I--Q---     6 perm 1f3f0000     0 65534 
> keyring   _uid.0: empty
> 
> add_key05.c:154: INFO: 1a300c72 I--Q---     1 perm 3f010000  1003     0 
> user      test1: 64
> 
> add_key05.c:154: INFO: 2e57738c I--Q---     1 perm 3f010000  1003     0 
> keyring   _tid: 1
> 
> the key num is 2 in /proc/key-users, I use this code to figure keyring 
>   _tid: 1(I don't know why has it) consumed data bytes.
> I guess that this is useless to run in
>> the case of testing for maximal number of keys, right? Can we put this
>> code into a function something as get_kernel_key_data_size() and call it
>> from the verify_max_bytes() only?
> Yes, it was only used in max_bytes.
>>
>> I guess that the SAFE_FILE_LINES_SCANF() intializes the max_key for the
>> max_keys test. Having a global variables that are initalized in random
>> places makes the code really confusing, can we avoid that if poissible
>> please?
> Ok. I will avoid this and make code clean.
>>
>> I guess that we can at least create:
>>
>> parse_proc_key_users(int *used_keys, int *max_key, int *used_bytes, 
>> *int max_bytes)
>>
>> function that would read the values into a local variables and copy the
>> results only if non-NULL pointers were passed.
>>
>> That way the verify_max_keys() would call:
>> parse_proc_key_users(NULL, &max_keys, NULL, NULL);
>> and use the result for the loop that adds the keys.
> Sound good, I will use it.
>>
>>> +        if (flag[n])
>>
>> Huh, why not just if (n)?
>>
> OK.
>>> +            verify_max_btyes();
>>> +        else
>>> +            verify_max_keys();
>>> +        exit(0);
>>> +    }
>>> +    tst_reap_children();
>>> +    clean_user();
>>
>> What is the reason to add and remove a user for each iteration?
>>
>> Aare you cleaning the keys that way?
> Yes, I planed to look for a wise (try KEYCTL_CLEAR
>   or KEYCTL_INVALIDATE)way but failed. Can you give me some
> suggestion?
>>
>>> +}
>>> +
>>> +static struct tst_test test = {
>>> +    .test = do_test,
>>> +    .tcnt = 2,
>>> +    .needs_root = 1,
>>> +    .forks_child = 1,
>>> +    .cleanup = clean_user,
>>> +    .bufs = (struct tst_buffers []) {
>>> +        {&keyring_buf, .size = 1},
>>> +        {&user_buf, .size = 64},
>>> +        {&user_buf1, .size = 64},
>>> +        {}
>>> +    },
>>> +    .tags = (const struct tst_tag[]) {
>>> +        {"linux-git", "a08bf91ce28"},
>>> +        {}
>>> +    }
>>> +};
>>> -- 
>>> 2.18.0
>>>
>>>
>>>
>>>
>>> -- 
>>> Mailing list info: https://lists.linux.it/listinfo/ltp
>>
diff mbox series

Patch

diff --git a/runtest/syscalls b/runtest/syscalls
index 577a4a527..df7bbcbf1 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -18,6 +18,7 @@  add_key01 add_key01
 add_key02 add_key02
 add_key03 add_key03
 add_key04 add_key04
+add_key05 add_key05
 
 adjtimex01 adjtimex01
 adjtimex02 adjtimex02
diff --git a/testcases/kernel/syscalls/add_key/.gitignore b/testcases/kernel/syscalls/add_key/.gitignore
index b9a04214d..f57dc2228 100644
--- a/testcases/kernel/syscalls/add_key/.gitignore
+++ b/testcases/kernel/syscalls/add_key/.gitignore
@@ -2,3 +2,4 @@ 
 /add_key02
 /add_key03
 /add_key04
+/add_key05
diff --git a/testcases/kernel/syscalls/add_key/add_key05.c b/testcases/kernel/syscalls/add_key/add_key05.c
new file mode 100644
index 000000000..0d5e9db28
--- /dev/null
+++ b/testcases/kernel/syscalls/add_key/add_key05.c
@@ -0,0 +1,180 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2020 FUJITSU LIMITED. All rights reserved.
+ * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
+ *
+ *Description:
+ * Test unprivileged user can support the number of keys and the
+ * number of bytes consumed in payloads of the keys.The defalut value
+ * is 200 and 20000.
+ * This is also a regresstion test for
+ * commit a08bf91ce28e ("KEYS: allow reaching the keys quotas exact")
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include "tst_test.h"
+#include "lapi/keyctl.h"
+
+static char *user_buf, *user_buf1, *keyring_buf;
+static const char *username = "ltp_add_key05";
+static int user_added;
+struct passwd *ltpuser;
+static unsigned int used_bytes, max_bytes, used_key, max_key, data_len;
+char fmt[1024];
+int flag[2] = {1, 0};
+
+void add_user(void)
+{
+	if (user_added)
+		return;
+
+	const char *const cmd_useradd[] = {"useradd", username, NULL};
+	int rc;
+
+	switch ((rc = tst_run_cmd(cmd_useradd, NULL, NULL, 1))) {
+	case 0:
+		user_added = 1;
+		ltpuser = SAFE_GETPWNAM(username);
+		break;
+	case 1:
+	case 255:
+		break;
+	default:
+		tst_brk(TBROK, "Useradd failed (%d)", rc);
+	}
+	sprintf(fmt, "%5u: %%*5d %%*d/%%*d %%d/%%d %%d/%%d", ltpuser->pw_uid);
+}
+
+void clean_user(void)
+{
+	if (!user_added)
+		return;
+
+	const char *const cmd_userdel[] = {"userdel", "-r", username, NULL};
+
+	if (tst_run_cmd(cmd_userdel, NULL, NULL, 1))
+		tst_res(TWARN | TERRNO, "'userdel -r %s' failed", username);
+	else
+		user_added = 0;
+}
+
+void verify_max_btyes(void)
+{
+	char *buf, *invalid_buf;
+	int plen, invalid_plen;
+
+	tst_res(TINFO, "test max bytes under unprivileged user");
+	invalid_plen = max_bytes - used_bytes - data_len - 8;
+	plen = invalid_plen - 1;
+	buf = tst_alloc(plen);
+	invalid_buf = tst_alloc(invalid_plen);
+
+	TEST(add_key("user", "test_inv", invalid_buf, invalid_plen, KEY_SPEC_THREAD_KEYRING));
+	if (TST_RET != -1)
+		tst_res(TFAIL, "add_key(test_inv) succeeded unexpectedltly");
+	else {
+		if (TST_ERR == EDQUOT)
+			tst_res(TPASS | TTERRNO, "add_key(test_inv) failed as expected");
+		else
+			tst_res(TFAIL | TTERRNO, "add_key(test_inv) failed expected EDQUOT got");
+	}
+
+	TEST(add_key("user", "test_max", buf, plen, KEY_SPEC_THREAD_KEYRING));
+	if (TST_RET != -1) {
+		tst_res(TPASS, "add_key(test_max) succeeded as expected");
+		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
+		if (used_bytes == max_bytes)
+			tst_res(TPASS, "allow reaching the max bytes exactly");
+		else
+			tst_res(TFAIL, "max used bytes %u, key allow max bytes %u", used_bytes, max_bytes);
+	} else
+		tst_res(TFAIL | TTERRNO, "add_key(test_max) failed unexpectedly");
+}
+
+void verify_max_keys(void)
+{
+	unsigned int i;
+	char desc[10];
+
+	tst_res(TINFO, "test max keys under unprivileged user");
+	for (i = used_key + 1; i < max_key; i++) {
+		sprintf(desc, "abc%d", i);
+		TEST(add_key("keyring", desc, keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
+		if (TST_RET == -1)
+			tst_res(TFAIL | TTERRNO, "add keyring key(%s) failed", desc);
+	}
+
+	TEST(add_key("keyring", "abc200", keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
+	if (TST_RET == -1) {
+		tst_res(TFAIL | TTERRNO, "add keyring key(abc200) failed");
+		goto count;
+	} else
+		tst_res(TPASS, "add keyring key(abc200) succedd");
+
+	TEST(add_key("keyring", "abc201", keyring_buf, 0, KEY_SPEC_THREAD_KEYRING));
+	if (TST_RET != -1) {
+		tst_res(TFAIL, "add keyring key(abc201) succeeded unexpectedly");
+		goto count;
+	} else {
+		if (TST_ERR == EDQUOT)
+			tst_res(TPASS | TTERRNO, "add keyring key(abc201) failed as expected over max key");
+		else
+			tst_res(TFAIL | TTERRNO, "add_keyring failed expected EDQUOT got");
+	}
+count:
+	SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
+	if (used_key == max_key)
+		tst_res(TPASS, "allow reaching the max key exactly");
+	else
+		tst_res(TFAIL, "max used key %u, key allow max key %u", used_key, max_key);
+}
+
+static void do_test(unsigned int n)
+{
+	add_user();
+	int f_used_bytes = 0;
+
+	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_brk(TFAIL | TTERRNO, "add key test1 failed");
+		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
+		f_used_bytes = used_bytes;
+
+		TEST(add_key("user", "test2", user_buf1, 64, KEY_SPEC_THREAD_KEYRING));
+		if (TST_RET == -1)
+			tst_brk(TFAIL | TTERRNO, "add key test2 failed");
+		SAFE_FILE_LINES_SCANF("/proc/key-users", fmt, &used_key, &max_key, &used_bytes, &max_bytes);
+
+		data_len = used_bytes - f_used_bytes - strlen("test1") - 1 - 64;
+		if (flag[n])
+			verify_max_btyes();
+		else
+			verify_max_keys();
+		exit(0);
+	}
+	tst_reap_children();
+	clean_user();
+}
+
+static struct tst_test test = {
+	.test = do_test,
+	.tcnt = 2,
+	.needs_root = 1,
+	.forks_child = 1,
+	.cleanup = clean_user,
+	.bufs = (struct tst_buffers []) {
+		{&keyring_buf, .size = 1},
+		{&user_buf, .size = 64},
+		{&user_buf1, .size = 64},
+		{}
+	},
+	.tags = (const struct tst_tag[]) {
+		{"linux-git", "a08bf91ce28"},
+		{}
+	}
+};