diff mbox

[Trusty] KEYS: request_key() should reget expired keys rather than give EKEYEXPIRED

Message ID 1427390098-27896-1-git-send-email-dariusz.gadomski@canonical.com
State New
Headers show

Commit Message

Dariusz Gadomski March 26, 2015, 5:14 p.m. UTC
From: David Howells <dhowells@redhat.com>

BugLink: https://bugs.launchpad.net/bugs/1124250

Since the keyring facility can be viewed as a cache (at least in some
applications), the local expiration time on the key should probably be viewed
as a 'needs updating after this time' property rather than an absolute 'anyone
now wanting to use this object is out of luck' property.

Since request_key() is the main interface for the usage of keys, this should
update or replace an expired key rather than issuing EKEYEXPIRED if the local
expiration has been reached (ie. it should refresh the cache).

For absolute conditions where refreshing the cache probably doesn't help, the
key can be negatively instantiated using KEYCTL_REJECT_KEY with EKEYEXPIRED
given as the error to issue.  This will still cause request_key() to return
EKEYEXPIRED as that was explicitly set.

In the future, if the key type has an update op available, we might want to
upcall with the expired key and allow the upcall to update it.  We would pass
a different operation name (the first column in /etc/request-key.conf) to the
request-key program.

request_key() returning EKEYEXPIRED is causing an NFS problem which Chuck
Lever describes thusly:

	After about 10 minutes, my NFSv4 functional tests fail because the
	ownership of the test files goes to "-2". Looking at /proc/keys
	shows that the id_resolv keys that map to my test user ID have
	expired. The ownership problem persists until the expired keys are
	purged from the keyring, and fresh keys are obtained.

	I bisected the problem to 3.13 commit b2a4df200d57 ("KEYS: Expand
	the capacity of a keyring"). This commit inadvertantly changes the
	API contract of the internal function keyring_search_aux().

	The root cause appears to be that b2a4df200d57 made "no state check"
	the default behavior. "No state check" means the keyring search
	iterator function skips checking the key's expiry timeout, and
	returns expired keys.  request_key_and_link() depends on getting
	an -EAGAIN result code to know when to perform an upcall to refresh
	an expired key.

This patch can be tested directly by:

	keyctl request2 user debug:fred a @s
	keyctl timeout %user:debug:fred 3
	sleep 4
	keyctl request2 user debug:fred a @s

Without the patch, the last command gives error EKEYEXPIRED, but with the
command it gives a new key.

Reported-by: Carl Hetherington <cth@carlh.net>
Reported-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Tested-by: Chuck Lever <chuck.lever@oracle.com>
(backported from commit 0b0a84154eff56913e91df29de5c3a03a0029e38 upstream)
Signed-off-by: Dariusz Gadomski <dariusz.gadomski@canonical.com>

Conflicts:
	security/keys/internal.h
	security/keys/request_key.c
---
 security/keys/internal.h    | 1 +
 security/keys/keyring.c     | 3 ++-
 security/keys/request_key.c | 3 ++-
 3 files changed, 5 insertions(+), 2 deletions(-)

Comments

Chris J Arges March 26, 2015, 6:39 p.m. UTC | #1
This backport matches what debian has, and had to be backported because
of the KEYRING_SEARCH_* flag that is removed. The bug now has the SRU
template and I've got verification from Dariusz that he's built/tested
this patch.

Acked-by: Chris J Arges <chris.j.arges@canonical.com>

On 03/26/2015 10:14 AM, Dariusz Gadomski wrote:
> From: David Howells <dhowells@redhat.com>
> 
> BugLink: https://bugs.launchpad.net/bugs/1124250
> 
> Since the keyring facility can be viewed as a cache (at least in some
> applications), the local expiration time on the key should probably be viewed
> as a 'needs updating after this time' property rather than an absolute 'anyone
> now wanting to use this object is out of luck' property.
> 
> Since request_key() is the main interface for the usage of keys, this should
> update or replace an expired key rather than issuing EKEYEXPIRED if the local
> expiration has been reached (ie. it should refresh the cache).
> 
> For absolute conditions where refreshing the cache probably doesn't help, the
> key can be negatively instantiated using KEYCTL_REJECT_KEY with EKEYEXPIRED
> given as the error to issue.  This will still cause request_key() to return
> EKEYEXPIRED as that was explicitly set.
> 
> In the future, if the key type has an update op available, we might want to
> upcall with the expired key and allow the upcall to update it.  We would pass
> a different operation name (the first column in /etc/request-key.conf) to the
> request-key program.
> 
> request_key() returning EKEYEXPIRED is causing an NFS problem which Chuck
> Lever describes thusly:
> 
> 	After about 10 minutes, my NFSv4 functional tests fail because the
> 	ownership of the test files goes to "-2". Looking at /proc/keys
> 	shows that the id_resolv keys that map to my test user ID have
> 	expired. The ownership problem persists until the expired keys are
> 	purged from the keyring, and fresh keys are obtained.
> 
> 	I bisected the problem to 3.13 commit b2a4df200d57 ("KEYS: Expand
> 	the capacity of a keyring"). This commit inadvertantly changes the
> 	API contract of the internal function keyring_search_aux().
> 
> 	The root cause appears to be that b2a4df200d57 made "no state check"
> 	the default behavior. "No state check" means the keyring search
> 	iterator function skips checking the key's expiry timeout, and
> 	returns expired keys.  request_key_and_link() depends on getting
> 	an -EAGAIN result code to know when to perform an upcall to refresh
> 	an expired key.
> 
> This patch can be tested directly by:
> 
> 	keyctl request2 user debug:fred a @s
> 	keyctl timeout %user:debug:fred 3
> 	sleep 4
> 	keyctl request2 user debug:fred a @s
> 
> Without the patch, the last command gives error EKEYEXPIRED, but with the
> command it gives a new key.
> 
> Reported-by: Carl Hetherington <cth@carlh.net>
> Reported-by: Chuck Lever <chuck.lever@oracle.com>
> Signed-off-by: David Howells <dhowells@redhat.com>
> Tested-by: Chuck Lever <chuck.lever@oracle.com>
> (backported from commit 0b0a84154eff56913e91df29de5c3a03a0029e38 upstream)
> Signed-off-by: Dariusz Gadomski <dariusz.gadomski@canonical.com>
> 
> Conflicts:
> 	security/keys/internal.h
> 	security/keys/request_key.c
> ---
>  security/keys/internal.h    | 1 +
>  security/keys/keyring.c     | 3 ++-
>  security/keys/request_key.c | 3 ++-
>  3 files changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index 80b2aac..6d5c72e 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -121,6 +121,7 @@ struct keyring_search_context {
>  #define KEYRING_SEARCH_NO_UPDATE_TIME	0x0008	/* Don't update times */
>  #define KEYRING_SEARCH_NO_CHECK_PERM	0x0010	/* Don't check permissions */
>  #define KEYRING_SEARCH_DETECT_TOO_DEEP	0x0020	/* Give an error on excessive depth */
> +#define KEYRING_SEARCH_SKIP_EXPIRED	0x0040	/* Ignore expired keys (intention to replace) */
>  
>  	int (*iterator)(const void *object, void *iterator_data);
>  
> diff --git a/security/keys/keyring.c b/security/keys/keyring.c
> index 2fb2576..c3e4800 100644
> --- a/security/keys/keyring.c
> +++ b/security/keys/keyring.c
> @@ -526,7 +526,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
>  		}
>  
>  		if (key->expiry && ctx->now.tv_sec >= key->expiry) {
> -			ctx->result = ERR_PTR(-EKEYEXPIRED);
> +			if (!(ctx->flags & KEYRING_SEARCH_SKIP_EXPIRED))
> +				ctx->result = ERR_PTR(-EKEYEXPIRED);
>  			kleave(" = %d [expire]", ctx->skipped_ret);
>  			goto skipped;
>  		}
> diff --git a/security/keys/request_key.c b/security/keys/request_key.c
> index 3814119..239896b 100644
> --- a/security/keys/request_key.c
> +++ b/security/keys/request_key.c
> @@ -533,7 +533,8 @@ struct key *request_key_and_link(struct key_type *type,
>  		.cred			= current_cred(),
>  		.match			= type->match,
>  		.match_data		= description,
> -		.flags			= KEYRING_SEARCH_LOOKUP_DIRECT,
> +		.flags			= (KEYRING_SEARCH_LOOKUP_DIRECT |
> +					   KEYRING_SEARCH_SKIP_EXPIRED),
>  	};
>  	struct key *key;
>  	key_ref_t key_ref;
>
Andy Whitcroft March 27, 2015, 9:14 a.m. UTC | #2
On Thu, Mar 26, 2015 at 10:14:58AM -0700, Dariusz Gadomski wrote:
> From: David Howells <dhowells@redhat.com>
> 
> BugLink: https://bugs.launchpad.net/bugs/1124250
> 
> Since the keyring facility can be viewed as a cache (at least in some
> applications), the local expiration time on the key should probably be viewed
> as a 'needs updating after this time' property rather than an absolute 'anyone
> now wanting to use this object is out of luck' property.
> 
> Since request_key() is the main interface for the usage of keys, this should
> update or replace an expired key rather than issuing EKEYEXPIRED if the local
> expiration has been reached (ie. it should refresh the cache).
> 
> For absolute conditions where refreshing the cache probably doesn't help, the
> key can be negatively instantiated using KEYCTL_REJECT_KEY with EKEYEXPIRED
> given as the error to issue.  This will still cause request_key() to return
> EKEYEXPIRED as that was explicitly set.
> 
> In the future, if the key type has an update op available, we might want to
> upcall with the expired key and allow the upcall to update it.  We would pass
> a different operation name (the first column in /etc/request-key.conf) to the
> request-key program.
> 
> request_key() returning EKEYEXPIRED is causing an NFS problem which Chuck
> Lever describes thusly:
> 
> 	After about 10 minutes, my NFSv4 functional tests fail because the
> 	ownership of the test files goes to "-2". Looking at /proc/keys
> 	shows that the id_resolv keys that map to my test user ID have
> 	expired. The ownership problem persists until the expired keys are
> 	purged from the keyring, and fresh keys are obtained.
> 
> 	I bisected the problem to 3.13 commit b2a4df200d57 ("KEYS: Expand
> 	the capacity of a keyring"). This commit inadvertantly changes the
> 	API contract of the internal function keyring_search_aux().
> 
> 	The root cause appears to be that b2a4df200d57 made "no state check"
> 	the default behavior. "No state check" means the keyring search
> 	iterator function skips checking the key's expiry timeout, and
> 	returns expired keys.  request_key_and_link() depends on getting
> 	an -EAGAIN result code to know when to perform an upcall to refresh
> 	an expired key.
> 
> This patch can be tested directly by:
> 
> 	keyctl request2 user debug:fred a @s
> 	keyctl timeout %user:debug:fred 3
> 	sleep 4
> 	keyctl request2 user debug:fred a @s
> 
> Without the patch, the last command gives error EKEYEXPIRED, but with the
> command it gives a new key.
> 
> Reported-by: Carl Hetherington <cth@carlh.net>
> Reported-by: Chuck Lever <chuck.lever@oracle.com>
> Signed-off-by: David Howells <dhowells@redhat.com>
> Tested-by: Chuck Lever <chuck.lever@oracle.com>
> (backported from commit 0b0a84154eff56913e91df29de5c3a03a0029e38 upstream)
> Signed-off-by: Dariusz Gadomski <dariusz.gadomski@canonical.com>
> 
> Conflicts:
> 	security/keys/internal.h
> 	security/keys/request_key.c
> ---
>  security/keys/internal.h    | 1 +
>  security/keys/keyring.c     | 3 ++-
>  security/keys/request_key.c | 3 ++-
>  3 files changed, 5 insertions(+), 2 deletions(-)
> 
> diff --git a/security/keys/internal.h b/security/keys/internal.h
> index 80b2aac..6d5c72e 100644
> --- a/security/keys/internal.h
> +++ b/security/keys/internal.h
> @@ -121,6 +121,7 @@ struct keyring_search_context {
>  #define KEYRING_SEARCH_NO_UPDATE_TIME	0x0008	/* Don't update times */
>  #define KEYRING_SEARCH_NO_CHECK_PERM	0x0010	/* Don't check permissions */
>  #define KEYRING_SEARCH_DETECT_TOO_DEEP	0x0020	/* Give an error on excessive depth */
> +#define KEYRING_SEARCH_SKIP_EXPIRED	0x0040	/* Ignore expired keys (intention to replace) */
>  
>  	int (*iterator)(const void *object, void *iterator_data);
>  
> diff --git a/security/keys/keyring.c b/security/keys/keyring.c
> index 2fb2576..c3e4800 100644
> --- a/security/keys/keyring.c
> +++ b/security/keys/keyring.c
> @@ -526,7 +526,8 @@ static int keyring_search_iterator(const void *object, void *iterator_data)
>  		}
>  
>  		if (key->expiry && ctx->now.tv_sec >= key->expiry) {
> -			ctx->result = ERR_PTR(-EKEYEXPIRED);
> +			if (!(ctx->flags & KEYRING_SEARCH_SKIP_EXPIRED))
> +				ctx->result = ERR_PTR(-EKEYEXPIRED);
>  			kleave(" = %d [expire]", ctx->skipped_ret);
>  			goto skipped;
>  		}
> diff --git a/security/keys/request_key.c b/security/keys/request_key.c
> index 3814119..239896b 100644
> --- a/security/keys/request_key.c
> +++ b/security/keys/request_key.c
> @@ -533,7 +533,8 @@ struct key *request_key_and_link(struct key_type *type,
>  		.cred			= current_cred(),
>  		.match			= type->match,
>  		.match_data		= description,
> -		.flags			= KEYRING_SEARCH_LOOKUP_DIRECT,
> +		.flags			= (KEYRING_SEARCH_LOOKUP_DIRECT |
> +					   KEYRING_SEARCH_SKIP_EXPIRED),
>  	};
>  	struct key *key;
>  	key_ref_t key_ref;

Looks to match upstream in intent, easy to test:

Acked-by: Andy Whitcroft <apw@canonical.com>

-apw
Andy Whitcroft March 27, 2015, 9:17 a.m. UTC | #3
Applied to trusty.

-apw
Stefan Bader March 27, 2015, 9:18 a.m. UTC | #4
Seems to implement what it claims, testable, adaptation of feature bit looks
reasonable.
diff mbox

Patch

diff --git a/security/keys/internal.h b/security/keys/internal.h
index 80b2aac..6d5c72e 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -121,6 +121,7 @@  struct keyring_search_context {
 #define KEYRING_SEARCH_NO_UPDATE_TIME	0x0008	/* Don't update times */
 #define KEYRING_SEARCH_NO_CHECK_PERM	0x0010	/* Don't check permissions */
 #define KEYRING_SEARCH_DETECT_TOO_DEEP	0x0020	/* Give an error on excessive depth */
+#define KEYRING_SEARCH_SKIP_EXPIRED	0x0040	/* Ignore expired keys (intention to replace) */
 
 	int (*iterator)(const void *object, void *iterator_data);
 
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index 2fb2576..c3e4800 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -526,7 +526,8 @@  static int keyring_search_iterator(const void *object, void *iterator_data)
 		}
 
 		if (key->expiry && ctx->now.tv_sec >= key->expiry) {
-			ctx->result = ERR_PTR(-EKEYEXPIRED);
+			if (!(ctx->flags & KEYRING_SEARCH_SKIP_EXPIRED))
+				ctx->result = ERR_PTR(-EKEYEXPIRED);
 			kleave(" = %d [expire]", ctx->skipped_ret);
 			goto skipped;
 		}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 3814119..239896b 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -533,7 +533,8 @@  struct key *request_key_and_link(struct key_type *type,
 		.cred			= current_cred(),
 		.match			= type->match,
 		.match_data		= description,
-		.flags			= KEYRING_SEARCH_LOOKUP_DIRECT,
+		.flags			= (KEYRING_SEARCH_LOOKUP_DIRECT |
+					   KEYRING_SEARCH_SKIP_EXPIRED),
 	};
 	struct key *key;
 	key_ref_t key_ref;