From: Andy Adamson <[email protected]>
This version: responded to Trond's comments - mostly rate limiting the
pr_warn's. Also, Jorge Mora did official regression testing on Version 4
and showed the pathset introduced no regressions.
See the testing section below.
-------
Version 4, responded to comments:
This code has been refactored since version 3, and the first two patches
are new.
Related code to destroy the gss context upon kdestoy that also uses some of the
underlying SUNRPC functionality will come in a separate patch set.
-----------
Version 3, responded to comments:
1) Changed "SUNRPC Fix rpc_verify_header error returns" into
"SUNRPC refactor rpcauth_checkverf error returns" which only returns -EACCES on
rpcauth_checkverf error in gss_validate if -EKEYEXPIRED is returned.
Rebased on 3.7-rc7 Trond's testing branch.
-------------
Version 2, responded to comments:
1) Just use high water mark
2) Move expiration testing into nfs_file_write
3) Added a patch to clean up rpc_verify_header error processing
Edited explanation from version 3:
We must avoid buffering a WRITE that is using a credential key (e.g. a GSS
context key) that is about to expire. We currently will paint ourselves into
a corner by returning success to the applciation for such a buffered WRITE,
only to discover that we do not have permission when we attempt to flush the
WRITE (and potentially associated COMMIT) to disk. This results in the
the application thinking it has written more to disk than it actually has.
Pages for buffered WRITEs are allocated in nfs_write_begin where we have an
nfs_open_context and associated rpc_cred. This is a generic rpc_cred, NOT
the gss_cred used in the actual WRITE RPC. Each WRITE RPC call takes the generic
rpc_cred (or uses the 'current_cred') uid and uses it to lookup the associated
gss_cred and gss_context in the call_refresh RPC state. So, there is a
one-to-one association between the nfs_open_context generic_cred and a
gss_cred with a matching uid and a valid non expired gss context.
We need to check the nfs_open_context generic cred 'underlying' gss_cred
gss_context gc_expiry in nfs_write_begin to determine if there is enough
time left in the gss_context lifetime to complete the buffered WRITEs.
I've added a credential key expiry watermark, RPC_KEY_EXPIRE_TIMEO set to 240
seconds as a default and can be set via a module parameter as we need to
ensure there is time for any dirty data to be flushed.
If a WRITE is using a credential with a key that will expire within
watermark seconds, we flush the inode in nfs_write_end and send only
NFS_FILE_SYNC WRITEs.
TESTING:
We've tested with and without this patchset mounting Kerberos shares, and
have found no regressions. We also validated that buffered writes are flushed
before the GSS context expires.
-->Andy
Andy Adamson (5):
SUNRPC: don't map EKEYEXPIRED to EACCES in call_refreshresult
NFS: Warn when attempting a buffered write or commit with an expired
credential
SUNRPC new rpc_credops to test credential expiry
NFS avoid expired credential keys for buffered writes
SUNRPC refactor rpcauth_checkverf error returns
fs/nfs/file.c | 20 ++++++++++-
fs/nfs/internal.h | 2 ++
fs/nfs/write.c | 38 ++++++++++++++++++++
include/linux/sunrpc/auth.h | 16 +++++++++
net/sunrpc/auth.c | 21 +++++++++++
net/sunrpc/auth_generic.c | 82 ++++++++++++++++++++++++++++++++++++++++++
net/sunrpc/auth_gss/auth_gss.c | 62 +++++++++++++++++++++++++++++---
net/sunrpc/auth_null.c | 4 +--
net/sunrpc/auth_unix.c | 4 +--
net/sunrpc/clnt.c | 19 +++++-----
10 files changed, 251 insertions(+), 17 deletions(-)
--
1.8.3.1
From: Andy Adamson <[email protected]>
Signed-off-by: Andy Adamson <[email protected]>
---
fs/nfs/write.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index f1bdb72..7b7d360 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -1330,6 +1330,14 @@ void nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
dprintk("NFS: %5u nfs_writeback_done (status %d)\n",
task->tk_pid, task->tk_status);
+ if (task->tk_status == -EKEYEXPIRED)
+ pr_warn_ratelimited("NFS: write attempt with expired "
+ "credential req %s/%lld, %u bytes @ offset %llu\n",
+ inode->i_sb->s_id,
+ (long long)NFS_FILEID(inode),
+ data->args.count,
+ (unsigned long long)data->args.offset);
+
/*
* ->write_done will attempt to use post-op attributes to detect
* conflicting writes by other clients. A strict interpretation
@@ -1556,6 +1564,9 @@ static void nfs_commit_done(struct rpc_task *task, void *calldata)
dprintk("NFS: %5u nfs_commit_done (status %d)\n",
task->tk_pid, task->tk_status);
+ if (task->tk_status == -EKEYEXPIRED)
+ pr_warn_ratelimited("NFS: commit attempt with expired "
+ "credential\n");
/* Call the NFS version-specific code */
NFS_PROTO(data->inode)->commit_done(task, data);
--
1.8.3.1
From: Andy Adamson <[email protected]>
This patch provides the RPC layer helper functions to allow NFS to manage
data in the face of expired credentials - such as avoiding buffered WRITEs
and COMMITs when the gss context will expire before the WRITEs are flushed
and COMMITs are sent.
These helper functions enable checking the expiration of an underlying
credential key for a generic rpc credential, e.g. the gss_cred gss context
gc_expiry which for Kerberos is set to the remaining TGT lifetime.
A new rpc_authops key_timeout is only defined for the generic auth.
A new rpc_credops crkey_to_expire is only defined for the generic cred.
A new rpc_credops crkey_timeout is only defined for the gss cred.
Set a credential key expiry watermark, RPC_KEY_EXPIRE_TIMEO set to 240 seconds
as a default and can be set via a module parameter as we need to ensure there
is time for any dirty data to be flushed.
If key_timeout is called on a credential with an underlying credential key that
will expire within watermark seconds, we set the RPC_CRED_KEY_EXPIRE_SOON
flag in the generic_cred acred so that the NFS layer can clean up prior to
key expiration.
Checking a generic credential's underlying credential involves a cred lookup.
To avoid this lookup in the normal case when the underlying credential has
a key that is valid (before the watermark), a notify flag is set in
the generic credential the first time the key_timeout is called. The
generic credential then stops checking the underlying credential key expiry, and
the underlying credential (gss_cred) match routine then checks the key
expiration upon each normal use and sets a flag in the associated generic
credential only when the key expiration is within the watermark.
This in turn signals the generic credential key_timeout to perform the extra
credential lookup thereafter.
Signed-off-by: Andy Adamson <[email protected]>
---
include/linux/sunrpc/auth.h | 16 +++++++++
net/sunrpc/auth.c | 21 +++++++++++
net/sunrpc/auth_generic.c | 82 ++++++++++++++++++++++++++++++++++++++++++
net/sunrpc/auth_gss/auth_gss.c | 55 ++++++++++++++++++++++++++--
4 files changed, 172 insertions(+), 2 deletions(-)
diff --git a/include/linux/sunrpc/auth.h b/include/linux/sunrpc/auth.h
index 0dd00f4..a79d042 100644
--- a/include/linux/sunrpc/auth.h
+++ b/include/linux/sunrpc/auth.h
@@ -24,12 +24,21 @@
struct rpcsec_gss_info;
+/* auth_cred ac_flags bits */
+enum {
+ RPC_CRED_NO_CRKEY_TIMEOUT = 0, /* underlying cred has no key timeout */
+ RPC_CRED_KEY_EXPIRE_SOON = 1, /* underlying cred key will expire soon */
+ RPC_CRED_NOTIFY_TIMEOUT = 2, /* nofity generic cred when underlying
+ key will expire soon */
+};
+
/* Work around the lack of a VFS credential */
struct auth_cred {
kuid_t uid;
kgid_t gid;
struct group_info *group_info;
const char *principal;
+ unsigned long ac_flags;
unsigned char machine_cred : 1;
};
@@ -108,6 +117,8 @@ struct rpc_authops {
rpc_authflavor_t (*info2flavor)(struct rpcsec_gss_info *);
int (*flavor2info)(rpc_authflavor_t,
struct rpcsec_gss_info *);
+ int (*key_timeout)(struct rpc_auth *,
+ struct rpc_cred *);
};
struct rpc_credops {
@@ -124,6 +135,8 @@ struct rpc_credops {
void *, __be32 *, void *);
int (*crunwrap_resp)(struct rpc_task *, kxdrdproc_t,
void *, __be32 *, void *);
+ int (*crkey_timeout)(struct rpc_cred *);
+ bool (*crkey_to_expire)(struct rpc_cred *);
};
extern const struct rpc_authops authunix_ops;
@@ -162,6 +175,9 @@ int rpcauth_uptodatecred(struct rpc_task *);
int rpcauth_init_credcache(struct rpc_auth *);
void rpcauth_destroy_credcache(struct rpc_auth *);
void rpcauth_clear_credcache(struct rpc_cred_cache *);
+int rpcauth_key_timeout_notify(struct rpc_auth *,
+ struct rpc_cred *);
+bool rpcauth_cred_key_to_expire(struct rpc_cred *);
static inline
struct rpc_cred * get_rpccred(struct rpc_cred *cred)
diff --git a/net/sunrpc/auth.c b/net/sunrpc/auth.c
index ed2fdd2..1741370 100644
--- a/net/sunrpc/auth.c
+++ b/net/sunrpc/auth.c
@@ -343,6 +343,27 @@ out_nocache:
EXPORT_SYMBOL_GPL(rpcauth_init_credcache);
/*
+ * Setup a credential key lifetime timeout notification
+ */
+int
+rpcauth_key_timeout_notify(struct rpc_auth *auth, struct rpc_cred *cred)
+{
+ if (!cred->cr_auth->au_ops->key_timeout)
+ return 0;
+ return cred->cr_auth->au_ops->key_timeout(auth, cred);
+}
+EXPORT_SYMBOL_GPL(rpcauth_key_timeout_notify);
+
+bool
+rpcauth_cred_key_to_expire(struct rpc_cred *cred)
+{
+ if (!cred->cr_ops->crkey_to_expire)
+ return false;
+ return cred->cr_ops->crkey_to_expire(cred);
+}
+EXPORT_SYMBOL_GPL(rpcauth_cred_key_to_expire);
+
+/*
* Destroy a list of credentials
*/
static inline
diff --git a/net/sunrpc/auth_generic.c b/net/sunrpc/auth_generic.c
index b6badaf..f6d84be 100644
--- a/net/sunrpc/auth_generic.c
+++ b/net/sunrpc/auth_generic.c
@@ -89,6 +89,7 @@ generic_create_cred(struct rpc_auth *auth, struct auth_cred *acred, int flags)
gcred->acred.uid = acred->uid;
gcred->acred.gid = acred->gid;
gcred->acred.group_info = acred->group_info;
+ gcred->acred.ac_flags = 0;
if (gcred->acred.group_info != NULL)
get_group_info(gcred->acred.group_info);
gcred->acred.machine_cred = acred->machine_cred;
@@ -182,11 +183,78 @@ void rpc_destroy_generic_auth(void)
rpcauth_destroy_credcache(&generic_auth);
}
+/*
+ * Test the the current time (now) against the underlying credential key expiry
+ * minus a timeout and setup notification.
+ *
+ * The normal case:
+ * If 'now' is before the key expiry minus RPC_KEY_EXPIRE_TIMEO, set
+ * the RPC_CRED_NOTIFY_TIMEOUT flag to setup the underlying credential
+ * rpc_credops crmatch routine to notify this generic cred when it's key
+ * expiration is within RPC_KEY_EXPIRE_TIMEO, and return 0.
+ *
+ * The error case:
+ * If the underlying cred lookup fails, return -EACCES.
+ *
+ * The 'almost' error case:
+ * If 'now' is within key expiry minus RPC_KEY_EXPIRE_TIMEO, but not within
+ * key expiry minus RPC_KEY_EXPIRE_FAIL, set the RPC_CRED_EXPIRE_SOON bit
+ * on the acred ac_flags and return 0.
+ */
+static int
+generic_key_timeout(struct rpc_auth *auth, struct rpc_cred *cred)
+{
+ struct auth_cred *acred = &container_of(cred, struct generic_cred,
+ gc_base)->acred;
+ struct rpc_cred *tcred;
+ int ret = 0;
+
+
+ /* Fast track for non crkey_timeout (no key) underlying credentials */
+ if (test_bit(RPC_CRED_NO_CRKEY_TIMEOUT, &acred->ac_flags))
+ return 0;
+
+ /* Fast track for the normal case */
+ if (test_bit(RPC_CRED_NOTIFY_TIMEOUT, &acred->ac_flags))
+ return 0;
+
+ /* lookup_cred either returns a valid referenced rpc_cred, or PTR_ERR */
+ tcred = auth->au_ops->lookup_cred(auth, acred, 0);
+ if (IS_ERR(tcred))
+ return -EACCES;
+
+ if (!tcred->cr_ops->crkey_timeout) {
+ set_bit(RPC_CRED_NO_CRKEY_TIMEOUT, &acred->ac_flags);
+ ret = 0;
+ goto out_put;
+ }
+
+ /* Test for the almost error case */
+ ret = tcred->cr_ops->crkey_timeout(tcred);
+ if (ret != 0) {
+ set_bit(RPC_CRED_KEY_EXPIRE_SOON, &acred->ac_flags);
+ ret = 0;
+ } else {
+ /* In case underlying cred key has been reset */
+ if (test_and_clear_bit(RPC_CRED_KEY_EXPIRE_SOON,
+ &acred->ac_flags))
+ dprintk("RPC: UID %d Credential key reset\n",
+ tcred->cr_uid);
+ /* set up fasttrack for the normal case */
+ set_bit(RPC_CRED_NOTIFY_TIMEOUT, &acred->ac_flags);
+ }
+
+out_put:
+ put_rpccred(tcred);
+ return ret;
+}
+
static const struct rpc_authops generic_auth_ops = {
.owner = THIS_MODULE,
.au_name = "Generic",
.lookup_cred = generic_lookup_cred,
.crcreate = generic_create_cred,
+ .key_timeout = generic_key_timeout,
};
static struct rpc_auth generic_auth = {
@@ -194,9 +262,23 @@ static struct rpc_auth generic_auth = {
.au_count = ATOMIC_INIT(0),
};
+static bool generic_key_to_expire(struct rpc_cred *cred)
+{
+ struct auth_cred *acred = &container_of(cred, struct generic_cred,
+ gc_base)->acred;
+ bool ret;
+
+ get_rpccred(cred);
+ ret = test_bit(RPC_CRED_KEY_EXPIRE_SOON, &acred->ac_flags);
+ put_rpccred(cred);
+
+ return ret;
+}
+
static const struct rpc_credops generic_credops = {
.cr_name = "Generic cred",
.crdestroy = generic_destroy_cred,
.crbind = generic_bind_cred,
.crmatch = generic_match,
+ .crkey_to_expire = generic_key_to_expire,
};
diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c
index fc2f78d..8b5526c 100644
--- a/net/sunrpc/auth_gss/auth_gss.c
+++ b/net/sunrpc/auth_gss/auth_gss.c
@@ -62,6 +62,9 @@ static const struct rpc_credops gss_nullops;
#define GSS_RETRY_EXPIRED 5
static unsigned int gss_expired_cred_retry_delay = GSS_RETRY_EXPIRED;
+#define GSS_KEY_EXPIRE_TIMEO 240
+static unsigned int gss_key_expire_timeo = GSS_KEY_EXPIRE_TIMEO;
+
#ifdef RPC_DEBUG
# define RPCDBG_FACILITY RPCDBG_AUTH
#endif
@@ -1126,10 +1129,32 @@ gss_cred_init(struct rpc_auth *auth, struct rpc_cred *cred)
return err;
}
+/*
+ * Returns -EACCES if GSS context is NULL or will expire within the
+ * timeout (miliseconds)
+ */
+static int
+gss_key_timeout(struct rpc_cred *rc)
+{
+ struct gss_cred *gss_cred = container_of(rc, struct gss_cred, gc_base);
+ unsigned long now = jiffies;
+ unsigned long expire;
+
+ if (gss_cred->gc_ctx == NULL)
+ return -EACCES;
+
+ expire = gss_cred->gc_ctx->gc_expiry - (gss_key_expire_timeo * HZ);
+
+ if (time_after(now, expire))
+ return -EACCES;
+ return 0;
+}
+
static int
gss_match(struct auth_cred *acred, struct rpc_cred *rc, int flags)
{
struct gss_cred *gss_cred = container_of(rc, struct gss_cred, gc_base);
+ int ret;
if (test_bit(RPCAUTH_CRED_NEW, &rc->cr_flags))
goto out;
@@ -1142,11 +1167,29 @@ out:
if (acred->principal != NULL) {
if (gss_cred->gc_principal == NULL)
return 0;
- return strcmp(acred->principal, gss_cred->gc_principal) == 0;
+ ret = strcmp(acred->principal, gss_cred->gc_principal) == 0;
+ goto check_expire;
}
if (gss_cred->gc_principal != NULL)
return 0;
- return uid_eq(rc->cr_uid, acred->uid);
+ ret = uid_eq(rc->cr_uid, acred->uid);
+
+check_expire:
+ if (ret == 0)
+ return ret;
+
+ /* Notify acred users of GSS context expiration timeout */
+ if (test_bit(RPC_CRED_NOTIFY_TIMEOUT, &acred->ac_flags) &&
+ (gss_key_timeout(rc) != 0)) {
+ /* test will now be done from generic cred */
+ test_and_clear_bit(RPC_CRED_NOTIFY_TIMEOUT, &acred->ac_flags);
+ /* tell NFS layer that key will expire soon */
+ set_bit(RPC_CRED_KEY_EXPIRE_SOON, &acred->ac_flags);
+ pr_warn_ratelimited("RPC: UID %d GSS context to expire "
+ "within %d seconds\n",
+ rc->cr_uid, gss_key_expire_timeo);
+ }
+ return ret;
}
/*
@@ -1675,6 +1718,7 @@ static const struct rpc_credops gss_credops = {
.crvalidate = gss_validate,
.crwrap_req = gss_wrap_req,
.crunwrap_resp = gss_unwrap_resp,
+ .crkey_timeout = gss_key_timeout,
};
static const struct rpc_credops gss_nullops = {
@@ -1762,5 +1806,12 @@ module_param_named(expired_cred_retry_delay,
MODULE_PARM_DESC(expired_cred_retry_delay, "Timeout (in seconds) until "
"the RPC engine retries an expired credential");
+module_param_named(key_expire_timeo,
+ gss_key_expire_timeo,
+ uint, 0644);
+MODULE_PARM_DESC(key_expire_timeo, "Time (in seconds) at the end of a "
+ "credential keys lifetime where the NFS layer cleans up "
+ "prior to key expiration");
+
module_init(init_rpcsec_gss)
module_exit(exit_rpcsec_gss)
--
1.8.3.1
From: Andy Adamson <[email protected]>
The NFS layer needs to know when a key has expired.
This change also returns -EKEYEXPIRED to the application, and the informative
"Key has expired" error message is displayed. The user then knows that
credential renewal is required.
Signed-off-by: Andy Adamson <[email protected]>
---
net/sunrpc/clnt.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index ecbc4e3..b544965 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -1423,9 +1423,9 @@ call_refreshresult(struct rpc_task *task)
return;
case -ETIMEDOUT:
rpc_delay(task, 3*HZ);
- case -EKEYEXPIRED:
case -EAGAIN:
status = -EACCES;
+ case -EKEYEXPIRED:
if (!task->tk_cred_retry)
break;
task->tk_cred_retry--;
--
1.8.3.1
From: Andy Adamson <[email protected]>
Most of the time an error from the credops crvalidate function means the
server has sent us a garbage verifier. The gss_validate function is the
exception where there is an -EACCES case if the user GSS_context on the client
has expired.
Signed-off-by: Andy Adamson <[email protected]>
---
net/sunrpc/auth_gss/auth_gss.c | 7 +++++--
net/sunrpc/auth_null.c | 4 ++--
net/sunrpc/auth_unix.c | 4 ++--
net/sunrpc/clnt.c | 17 ++++++++++-------
4 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/net/sunrpc/auth_gss/auth_gss.c b/net/sunrpc/auth_gss/auth_gss.c
index 8b5526c..f25dfd8 100644
--- a/net/sunrpc/auth_gss/auth_gss.c
+++ b/net/sunrpc/auth_gss/auth_gss.c
@@ -1335,6 +1335,7 @@ gss_validate(struct rpc_task *task, __be32 *p)
struct xdr_netobj mic;
u32 flav,len;
u32 maj_stat;
+ __be32 *ret = ERR_PTR(-EIO);
dprintk("RPC: %5u %s\n", task->tk_pid, __func__);
@@ -1350,6 +1351,7 @@ gss_validate(struct rpc_task *task, __be32 *p)
mic.data = (u8 *)p;
mic.len = len;
+ ret = ERR_PTR(-EACCES);
maj_stat = gss_verify_mic(ctx->gc_gss_ctx, &verf_buf, &mic);
if (maj_stat == GSS_S_CONTEXT_EXPIRED)
clear_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
@@ -1367,8 +1369,9 @@ gss_validate(struct rpc_task *task, __be32 *p)
return p + XDR_QUADLEN(len);
out_bad:
gss_put_ctx(ctx);
- dprintk("RPC: %5u %s failed.\n", task->tk_pid, __func__);
- return NULL;
+ dprintk("RPC: %5u %s failed ret %ld.\n", task->tk_pid, __func__,
+ PTR_ERR(ret));
+ return ret;
}
static void gss_wrap_req_encode(kxdreproc_t encode, struct rpc_rqst *rqstp,
diff --git a/net/sunrpc/auth_null.c b/net/sunrpc/auth_null.c
index a5c36c0..1e7d4a1 100644
--- a/net/sunrpc/auth_null.c
+++ b/net/sunrpc/auth_null.c
@@ -88,13 +88,13 @@ nul_validate(struct rpc_task *task, __be32 *p)
flavor = ntohl(*p++);
if (flavor != RPC_AUTH_NULL) {
printk("RPC: bad verf flavor: %u\n", flavor);
- return NULL;
+ return ERR_PTR(-EIO);
}
size = ntohl(*p++);
if (size != 0) {
printk("RPC: bad verf size: %u\n", size);
- return NULL;
+ return ERR_PTR(-EIO);
}
return p;
diff --git a/net/sunrpc/auth_unix.c b/net/sunrpc/auth_unix.c
index dc37021..8bfe1b7 100644
--- a/net/sunrpc/auth_unix.c
+++ b/net/sunrpc/auth_unix.c
@@ -192,13 +192,13 @@ unx_validate(struct rpc_task *task, __be32 *p)
flavor != RPC_AUTH_UNIX &&
flavor != RPC_AUTH_SHORT) {
printk("RPC: bad verf flavor: %u\n", flavor);
- return NULL;
+ return ERR_PTR(-EIO);
}
size = ntohl(*p++);
if (size > RPC_MAX_AUTH_SIZE) {
printk("RPC: giant verf size: %u\n", size);
- return NULL;
+ return ERR_PTR(-EIO);
}
task->tk_rqstp->rq_cred->cr_auth->au_rslack = (size >> 2) + 2;
p += (size >> 2);
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index b544965..b98b314 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -2091,7 +2091,8 @@ rpc_verify_header(struct rpc_task *task)
dprintk("RPC: %5u %s: XDR representation not a multiple of"
" 4 bytes: 0x%x\n", task->tk_pid, __func__,
task->tk_rqstp->rq_rcv_buf.len);
- goto out_eio;
+ error = -EIO;
+ goto out_err;
}
if ((len -= 3) < 0)
goto out_overflow;
@@ -2100,6 +2101,7 @@ rpc_verify_header(struct rpc_task *task)
if ((n = ntohl(*p++)) != RPC_REPLY) {
dprintk("RPC: %5u %s: not an RPC reply: %x\n",
task->tk_pid, __func__, n);
+ error = -EIO;
goto out_garbage;
}
@@ -2118,7 +2120,8 @@ rpc_verify_header(struct rpc_task *task)
dprintk("RPC: %5u %s: RPC call rejected, "
"unknown error: %x\n",
task->tk_pid, __func__, n);
- goto out_eio;
+ error = -EIO;
+ goto out_err;
}
if (--len < 0)
goto out_overflow;
@@ -2163,9 +2166,11 @@ rpc_verify_header(struct rpc_task *task)
task->tk_pid, __func__, n);
goto out_err;
}
- if (!(p = rpcauth_checkverf(task, p))) {
- dprintk("RPC: %5u %s: auth check failed\n",
- task->tk_pid, __func__);
+ p = rpcauth_checkverf(task, p);
+ if (IS_ERR(p)) {
+ error = PTR_ERR(p);
+ dprintk("RPC: %5u %s: auth check failed with %d\n",
+ task->tk_pid, __func__, error);
goto out_garbage; /* bad verifier, retry */
}
len = p - (__be32 *)iov->iov_base - 1;
@@ -2218,8 +2223,6 @@ out_garbage:
out_retry:
return ERR_PTR(-EAGAIN);
}
-out_eio:
- error = -EIO;
out_err:
rpc_exit(task, error);
dprintk("RPC: %5u %s: call failed with error %d\n", task->tk_pid,
--
1.8.3.1
From: Andy Adamson <[email protected]>
We must avoid buffering a WRITE that is using a credential key (e.g. a GSS
context key) that is about to expire or has expired. We currently will
paint ourselves into a corner by returning success to the applciation
for such a buffered WRITE, only to discover that we do not have permission when
we attempt to flush the WRITE (and potentially associated COMMIT) to disk.
Use the RPC layer credential key timeout and expire routines which use a
a watermark, gss_key_expire_timeo. We test the key in nfs_file_write.
If a WRITE is using a credential with a key that will expire within
watermark seconds, flush the inode in nfs_write_end and send only
NFS_FILE_SYNC WRITEs by adding nfs_ctx_key_to_expire to nfs_need_sync_write.
Note that this results in single page NFS_FILE_SYNC WRITEs.
Signed-off-by: Andy Adamson <[email protected]>
---
fs/nfs/file.c | 20 +++++++++++++++++++-
fs/nfs/internal.h | 2 ++
fs/nfs/write.c | 27 +++++++++++++++++++++++++++
3 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/fs/nfs/file.c b/fs/nfs/file.c
index 94e94bd..69f164d 100644
--- a/fs/nfs/file.c
+++ b/fs/nfs/file.c
@@ -406,6 +406,7 @@ static int nfs_write_end(struct file *file, struct address_space *mapping,
struct page *page, void *fsdata)
{
unsigned offset = pos & (PAGE_CACHE_SIZE - 1);
+ struct nfs_open_context *ctx = nfs_file_open_context(file);
int status;
dfprintk(PAGECACHE, "NFS: write_end(%s/%s(%ld), %u@%lld)\n",
@@ -441,6 +442,18 @@ static int nfs_write_end(struct file *file, struct address_space *mapping,
if (status < 0)
return status;
NFS_I(mapping->host)->write_io += copied;
+
+ if (nfs_ctx_key_to_expire(ctx)) {
+ pr_warn_ratelimited("NFS: Credential Key to expire. "
+ "Flush %s/%s(%ld)\n",
+ file->f_path.dentry->d_parent->d_name.name,
+ file->f_path.dentry->d_name.name,
+ mapping->host->i_ino);
+ status = nfs_wb_all(mapping->host);
+ if (status < 0)
+ return status;
+ }
+
return copied;
}
@@ -637,7 +650,8 @@ static int nfs_need_sync_write(struct file *filp, struct inode *inode)
if (IS_SYNC(inode) || (filp->f_flags & O_DSYNC))
return 1;
ctx = nfs_file_open_context(filp);
- if (test_bit(NFS_CONTEXT_ERROR_WRITE, &ctx->flags))
+ if (test_bit(NFS_CONTEXT_ERROR_WRITE, &ctx->flags) ||
+ nfs_ctx_key_to_expire(ctx))
return 1;
return 0;
}
@@ -651,6 +665,10 @@ ssize_t nfs_file_write(struct kiocb *iocb, const struct iovec *iov,
ssize_t result;
size_t count = iov_length(iov, nr_segs);
+ result = nfs_key_timeout_notify(iocb->ki_filp, inode);
+ if (result)
+ return result;
+
if (iocb->ki_filp->f_flags & O_DIRECT)
return nfs_file_direct_write(iocb, iov, nr_segs, pos, true);
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 4aa20ba..47c76f9 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -434,6 +434,8 @@ void nfs_request_remove_commit_list(struct nfs_page *req,
void nfs_init_cinfo(struct nfs_commit_info *cinfo,
struct inode *inode,
struct nfs_direct_req *dreq);
+int nfs_key_timeout_notify(struct file *filp, struct inode *inode);
+bool nfs_ctx_key_to_expire(struct nfs_open_context *ctx);
#ifdef CONFIG_MIGRATION
extern int nfs_migrate_page(struct address_space *,
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index 7b7d360..eefe498 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -874,6 +874,33 @@ int nfs_flush_incompatible(struct file *file, struct page *page)
}
/*
+ * Avoid buffered writes when a open context credential's key would
+ * expire soon.
+ *
+ * Returns -EACCES if the key will expire within RPC_KEY_EXPIRE_FAIL.
+ *
+ * Return 0 and set a credential flag which triggers the inode to flush
+ * and performs NFS_FILE_SYNC writes if the key will expired within
+ * RPC_KEY_EXPIRE_TIMEO.
+ */
+int
+nfs_key_timeout_notify(struct file *filp, struct inode *inode)
+{
+ struct nfs_open_context *ctx = nfs_file_open_context(filp);
+ struct rpc_auth *auth = NFS_SERVER(inode)->client->cl_auth;
+
+ return rpcauth_key_timeout_notify(auth, ctx->cred);
+}
+
+/*
+ * Test if the open context credential key is marked to expire soon.
+ */
+bool nfs_ctx_key_to_expire(struct nfs_open_context *ctx)
+{
+ return rpcauth_cred_key_to_expire(ctx->cred);
+}
+
+/*
* If the page cache is marked as unsafe or invalid, then we can't rely on
* the PageUptodate() flag. In this case, we will need to turn off
* write optimisations that depend on the page contents being correct.
--
1.8.3.1
T24gV2VkLCAyMDEzLTA4LTE0IGF0IDExOjU5IC0wNDAwLCBhbmRyb3NAbmV0YXBwLmNvbSB3cm90
ZToNCj4gRnJvbTogQW5keSBBZGFtc29uIDxhbmRyb3NAbmV0YXBwLmNvbT4NCj4gDQo+IFdlIG11
c3QgYXZvaWQgYnVmZmVyaW5nIGEgV1JJVEUgdGhhdCBpcyB1c2luZyBhIGNyZWRlbnRpYWwga2V5
IChlLmcuIGEgR1NTDQo+IGNvbnRleHQga2V5KSB0aGF0IGlzIGFib3V0IHRvIGV4cGlyZSBvciBo
YXMgZXhwaXJlZC4gIFdlIGN1cnJlbnRseSB3aWxsDQo+IHBhaW50IG91cnNlbHZlcyBpbnRvIGEg
Y29ybmVyIGJ5IHJldHVybmluZyBzdWNjZXNzIHRvIHRoZSBhcHBsY2lhdGlvbg0KPiBmb3Igc3Vj
aCBhIGJ1ZmZlcmVkIFdSSVRFLCBvbmx5IHRvIGRpc2NvdmVyIHRoYXQgd2UgZG8gbm90IGhhdmUg
cGVybWlzc2lvbiB3aGVuDQo+IHdlIGF0dGVtcHQgdG8gZmx1c2ggdGhlIFdSSVRFIChhbmQgcG90
ZW50aWFsbHkgYXNzb2NpYXRlZCBDT01NSVQpIHRvIGRpc2suDQo+IA0KPiBVc2UgdGhlIFJQQyBs
YXllciBjcmVkZW50aWFsIGtleSB0aW1lb3V0IGFuZCBleHBpcmUgcm91dGluZXMgd2hpY2ggdXNl
IGENCj4gYSB3YXRlcm1hcmssIGdzc19rZXlfZXhwaXJlX3RpbWVvLiBXZSB0ZXN0IHRoZSBrZXkg
aW4gbmZzX2ZpbGVfd3JpdGUuDQo+IA0KPiBJZiBhIFdSSVRFIGlzIHVzaW5nIGEgY3JlZGVudGlh
bCB3aXRoIGEga2V5IHRoYXQgd2lsbCBleHBpcmUgd2l0aGluDQo+IHdhdGVybWFyayBzZWNvbmRz
LCBmbHVzaCB0aGUgaW5vZGUgaW4gbmZzX3dyaXRlX2VuZCBhbmQgc2VuZCBvbmx5DQo+IE5GU19G
SUxFX1NZTkMgV1JJVEVzIGJ5IGFkZGluZyBuZnNfY3R4X2tleV90b19leHBpcmUgdG8gbmZzX25l
ZWRfc3luY193cml0ZS4NCj4gTm90ZSB0aGF0IHRoaXMgcmVzdWx0cyBpbiBzaW5nbGUgcGFnZSBO
RlNfRklMRV9TWU5DIFdSSVRFcy4NCj4gDQo+IFNpZ25lZC1vZmYtYnk6IEFuZHkgQWRhbXNvbiA8
YW5kcm9zQG5ldGFwcC5jb20+DQo+IC0tLQ0KPiAgZnMvbmZzL2ZpbGUuYyAgICAgfCAyMCArKysr
KysrKysrKysrKysrKysrLQ0KPiAgZnMvbmZzL2ludGVybmFsLmggfCAgMiArKw0KPiAgZnMvbmZz
L3dyaXRlLmMgICAgfCAyNyArKysrKysrKysrKysrKysrKysrKysrKysrKysNCj4gIDMgZmlsZXMg
Y2hhbmdlZCwgNDggaW5zZXJ0aW9ucygrKSwgMSBkZWxldGlvbigtKQ0KPiANCj4gZGlmZiAtLWdp
dCBhL2ZzL25mcy9maWxlLmMgYi9mcy9uZnMvZmlsZS5jDQo+IGluZGV4IDk0ZTk0YmQuLjY5ZjE2
NGQgMTAwNjQ0DQo+IC0tLSBhL2ZzL25mcy9maWxlLmMNCj4gKysrIGIvZnMvbmZzL2ZpbGUuYw0K
PiBAQCAtNDA2LDYgKzQwNiw3IEBAIHN0YXRpYyBpbnQgbmZzX3dyaXRlX2VuZChzdHJ1Y3QgZmls
ZSAqZmlsZSwgc3RydWN0IGFkZHJlc3Nfc3BhY2UgKm1hcHBpbmcsDQo+ICAJCQlzdHJ1Y3QgcGFn
ZSAqcGFnZSwgdm9pZCAqZnNkYXRhKQ0KPiAgew0KPiAgCXVuc2lnbmVkIG9mZnNldCA9IHBvcyAm
IChQQUdFX0NBQ0hFX1NJWkUgLSAxKTsNCj4gKwlzdHJ1Y3QgbmZzX29wZW5fY29udGV4dCAqY3R4
ID0gbmZzX2ZpbGVfb3Blbl9jb250ZXh0KGZpbGUpOw0KPiAgCWludCBzdGF0dXM7DQo+ICANCj4g
IAlkZnByaW50ayhQQUdFQ0FDSEUsICJORlM6IHdyaXRlX2VuZCglcy8lcyglbGQpLCAldUAlbGxk
KVxuIiwNCj4gQEAgLTQ0MSw2ICs0NDIsMTggQEAgc3RhdGljIGludCBuZnNfd3JpdGVfZW5kKHN0
cnVjdCBmaWxlICpmaWxlLCBzdHJ1Y3QgYWRkcmVzc19zcGFjZSAqbWFwcGluZywNCj4gIAlpZiAo
c3RhdHVzIDwgMCkNCj4gIAkJcmV0dXJuIHN0YXR1czsNCj4gIAlORlNfSShtYXBwaW5nLT5ob3N0
KS0+d3JpdGVfaW8gKz0gY29waWVkOw0KPiArDQo+ICsJaWYgKG5mc19jdHhfa2V5X3RvX2V4cGly
ZShjdHgpKSB7DQo+ICsJCXByX3dhcm5fcmF0ZWxpbWl0ZWQoIk5GUzoJQ3JlZGVudGlhbCBLZXkg
dG8gZXhwaXJlLiAiDQo+ICsJCQkiRmx1c2ggJXMvJXMoJWxkKVxuIiwNCj4gKwkJCWZpbGUtPmZf
cGF0aC5kZW50cnktPmRfcGFyZW50LT5kX25hbWUubmFtZSwNCj4gKwkJCWZpbGUtPmZfcGF0aC5k
ZW50cnktPmRfbmFtZS5uYW1lLA0KPiArCQkJbWFwcGluZy0+aG9zdC0+aV9pbm8pOw0KPiArCQlz
dGF0dXMgPSBuZnNfd2JfYWxsKG1hcHBpbmctPmhvc3QpOw0KPiArCQlpZiAoc3RhdHVzIDwgMCkN
Cj4gKwkJCXJldHVybiBzdGF0dXM7DQo+ICsJfQ0KPiArDQo+ICAJcmV0dXJuIGNvcGllZDsNCj4g
IH0NCj4gIA0KPiBAQCAtNjM3LDcgKzY1MCw4IEBAIHN0YXRpYyBpbnQgbmZzX25lZWRfc3luY193
cml0ZShzdHJ1Y3QgZmlsZSAqZmlscCwgc3RydWN0IGlub2RlICppbm9kZSkNCj4gIAlpZiAoSVNf
U1lOQyhpbm9kZSkgfHwgKGZpbHAtPmZfZmxhZ3MgJiBPX0RTWU5DKSkNCj4gIAkJcmV0dXJuIDE7
DQo+ICAJY3R4ID0gbmZzX2ZpbGVfb3Blbl9jb250ZXh0KGZpbHApOw0KPiAtCWlmICh0ZXN0X2Jp
dChORlNfQ09OVEVYVF9FUlJPUl9XUklURSwgJmN0eC0+ZmxhZ3MpKQ0KPiArCWlmICh0ZXN0X2Jp
dChORlNfQ09OVEVYVF9FUlJPUl9XUklURSwgJmN0eC0+ZmxhZ3MpIHx8DQo+ICsJICAgIG5mc19j
dHhfa2V5X3RvX2V4cGlyZShjdHgpKQ0KPiAgCQlyZXR1cm4gMTsNCj4gIAlyZXR1cm4gMDsNCj4g
IH0NCj4gQEAgLTY1MSw2ICs2NjUsMTAgQEAgc3NpemVfdCBuZnNfZmlsZV93cml0ZShzdHJ1Y3Qg
a2lvY2IgKmlvY2IsIGNvbnN0IHN0cnVjdCBpb3ZlYyAqaW92LA0KPiAgCXNzaXplX3QgcmVzdWx0
Ow0KPiAgCXNpemVfdCBjb3VudCA9IGlvdl9sZW5ndGgoaW92LCBucl9zZWdzKTsNCj4gIA0KPiAr
CXJlc3VsdCA9IG5mc19rZXlfdGltZW91dF9ub3RpZnkoaW9jYi0+a2lfZmlscCwgaW5vZGUpOw0K
PiArCWlmIChyZXN1bHQpDQo+ICsJCXJldHVybiByZXN1bHQ7DQo+ICsNCj4gIAlpZiAoaW9jYi0+
a2lfZmlscC0+Zl9mbGFncyAmIE9fRElSRUNUKQ0KPiAgCQlyZXR1cm4gbmZzX2ZpbGVfZGlyZWN0
X3dyaXRlKGlvY2IsIGlvdiwgbnJfc2VncywgcG9zLCB0cnVlKTsNCj4gIA0KPiBkaWZmIC0tZ2l0
IGEvZnMvbmZzL2ludGVybmFsLmggYi9mcy9uZnMvaW50ZXJuYWwuaA0KPiBpbmRleCA0YWEyMGJh
Li40N2M3NmY5IDEwMDY0NA0KPiAtLS0gYS9mcy9uZnMvaW50ZXJuYWwuaA0KPiArKysgYi9mcy9u
ZnMvaW50ZXJuYWwuaA0KPiBAQCAtNDM0LDYgKzQzNCw4IEBAIHZvaWQgbmZzX3JlcXVlc3RfcmVt
b3ZlX2NvbW1pdF9saXN0KHN0cnVjdCBuZnNfcGFnZSAqcmVxLA0KPiAgdm9pZCBuZnNfaW5pdF9j
aW5mbyhzdHJ1Y3QgbmZzX2NvbW1pdF9pbmZvICpjaW5mbywNCj4gIAkJICAgIHN0cnVjdCBpbm9k
ZSAqaW5vZGUsDQo+ICAJCSAgICBzdHJ1Y3QgbmZzX2RpcmVjdF9yZXEgKmRyZXEpOw0KPiAraW50
IG5mc19rZXlfdGltZW91dF9ub3RpZnkoc3RydWN0IGZpbGUgKmZpbHAsIHN0cnVjdCBpbm9kZSAq
aW5vZGUpOw0KPiArYm9vbCBuZnNfY3R4X2tleV90b19leHBpcmUoc3RydWN0IG5mc19vcGVuX2Nv
bnRleHQgKmN0eCk7DQo+ICANCj4gICNpZmRlZiBDT05GSUdfTUlHUkFUSU9ODQo+ICBleHRlcm4g
aW50IG5mc19taWdyYXRlX3BhZ2Uoc3RydWN0IGFkZHJlc3Nfc3BhY2UgKiwNCj4gZGlmZiAtLWdp
dCBhL2ZzL25mcy93cml0ZS5jIGIvZnMvbmZzL3dyaXRlLmMNCj4gaW5kZXggN2I3ZDM2MC4uZWVm
ZTQ5OCAxMDA2NDQNCj4gLS0tIGEvZnMvbmZzL3dyaXRlLmMNCj4gKysrIGIvZnMvbmZzL3dyaXRl
LmMNCj4gQEAgLTg3NCw2ICs4NzQsMzMgQEAgaW50IG5mc19mbHVzaF9pbmNvbXBhdGlibGUoc3Ry
dWN0IGZpbGUgKmZpbGUsIHN0cnVjdCBwYWdlICpwYWdlKQ0KPiAgfQ0KPiAgDQo+ICAvKg0KPiAr
ICogQXZvaWQgYnVmZmVyZWQgd3JpdGVzIHdoZW4gYSBvcGVuIGNvbnRleHQgY3JlZGVudGlhbCdz
IGtleSB3b3VsZA0KPiArICogZXhwaXJlIHNvb24uDQo+ICsgKg0KPiArICogUmV0dXJucyAtRUFD
Q0VTIGlmIHRoZSBrZXkgd2lsbCBleHBpcmUgd2l0aGluIFJQQ19LRVlfRVhQSVJFX0ZBSUwuDQo+
ICsgKg0KPiArICogUmV0dXJuIDAgYW5kIHNldCBhIGNyZWRlbnRpYWwgZmxhZyB3aGljaCB0cmln
Z2VycyB0aGUgaW5vZGUgdG8gZmx1c2gNCj4gKyAqIGFuZCBwZXJmb3JtcyAgTkZTX0ZJTEVfU1lO
QyB3cml0ZXMgaWYgdGhlIGtleSB3aWxsIGV4cGlyZWQgd2l0aGluDQo+ICsgKiBSUENfS0VZX0VY
UElSRV9USU1FTy4NCj4gKyAqLw0KPiAraW50DQo+ICtuZnNfa2V5X3RpbWVvdXRfbm90aWZ5KHN0
cnVjdCBmaWxlICpmaWxwLCBzdHJ1Y3QgaW5vZGUgKmlub2RlKQ0KPiArew0KPiArCXN0cnVjdCBu
ZnNfb3Blbl9jb250ZXh0ICpjdHggPSBuZnNfZmlsZV9vcGVuX2NvbnRleHQoZmlscCk7DQo+ICsJ
c3RydWN0IHJwY19hdXRoICphdXRoID0gTkZTX1NFUlZFUihpbm9kZSktPmNsaWVudC0+Y2xfYXV0
aDsNCj4gKw0KPiArCXJldHVybiBycGNhdXRoX2tleV90aW1lb3V0X25vdGlmeShhdXRoLCBjdHgt
PmNyZWQpOw0KPiArfQ0KPiArDQo+ICsvKg0KPiArICogVGVzdCBpZiB0aGUgb3BlbiBjb250ZXh0
IGNyZWRlbnRpYWwga2V5IGlzIG1hcmtlZCB0byBleHBpcmUgc29vbi4NCj4gKyAqLw0KPiArYm9v
bCBuZnNfY3R4X2tleV90b19leHBpcmUoc3RydWN0IG5mc19vcGVuX2NvbnRleHQgKmN0eCkNCj4g
K3sNCj4gKwlyZXR1cm4gcnBjYXV0aF9jcmVkX2tleV90b19leHBpcmUoY3R4LT5jcmVkKTsNCj4g
K30NCj4gKw0KPiArLyoNCj4gICAqIElmIHRoZSBwYWdlIGNhY2hlIGlzIG1hcmtlZCBhcyB1bnNh
ZmUgb3IgaW52YWxpZCwgdGhlbiB3ZSBjYW4ndCByZWx5IG9uDQo+ICAgKiB0aGUgUGFnZVVwdG9k
YXRlKCkgZmxhZy4gSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIG5lZWQgdG8gdHVybiBvZmYNCj4gICAq
IHdyaXRlIG9wdGltaXNhdGlvbnMgdGhhdCBkZXBlbmQgb24gdGhlIHBhZ2UgY29udGVudHMgYmVp
bmcgY29ycmVjdC4NCg0KQXBwbGllZCwgbWludXMgdGhlIHByaW50ay4uLg0KDQotLSANClRyb25k
IE15a2xlYnVzdA0KTGludXggTkZTIGNsaWVudCBtYWludGFpbmVyDQoNCk5ldEFwcA0KVHJvbmQu
TXlrbGVidXN0QG5ldGFwcC5jb20NCnd3dy5uZXRhcHAuY29tDQo=
T24gV2VkLCAyMDEzLTA4LTE0IGF0IDExOjU5IC0wNDAwLCBhbmRyb3NAbmV0YXBwLmNvbSB3cm90
ZToNCj4gRnJvbTogQW5keSBBZGFtc29uIDxhbmRyb3NAbmV0YXBwLmNvbT4NCj4gDQo+IFRoZSBO
RlMgbGF5ZXIgbmVlZHMgdG8ga25vdyB3aGVuIGEga2V5IGhhcyBleHBpcmVkLg0KPiBUaGlzIGNo
YW5nZSBhbHNvIHJldHVybnMgLUVLRVlFWFBJUkVEIHRvIHRoZSBhcHBsaWNhdGlvbiwgYW5kIHRo
ZSBpbmZvcm1hdGl2ZQ0KPiAiS2V5IGhhcyBleHBpcmVkIiBlcnJvciBtZXNzYWdlIGlzIGRpc3Bs
YXllZC4gVGhlIHVzZXIgdGhlbiBrbm93cyB0aGF0DQo+IGNyZWRlbnRpYWwgcmVuZXdhbCBpcyBy
ZXF1aXJlZC4NCj4gDQo+IFNpZ25lZC1vZmYtYnk6IEFuZHkgQWRhbXNvbiA8YW5kcm9zQG5ldGFw
cC5jb20+DQo+IC0tLQ0KPiAgbmV0L3N1bnJwYy9jbG50LmMgfCAyICstDQo+ICAxIGZpbGUgY2hh
bmdlZCwgMSBpbnNlcnRpb24oKyksIDEgZGVsZXRpb24oLSkNCj4gDQo+IGRpZmYgLS1naXQgYS9u
ZXQvc3VucnBjL2NsbnQuYyBiL25ldC9zdW5ycGMvY2xudC5jDQo+IGluZGV4IGVjYmM0ZTMuLmI1
NDQ5NjUgMTAwNjQ0DQo+IC0tLSBhL25ldC9zdW5ycGMvY2xudC5jDQo+ICsrKyBiL25ldC9zdW5y
cGMvY2xudC5jDQo+IEBAIC0xNDIzLDkgKzE0MjMsOSBAQCBjYWxsX3JlZnJlc2hyZXN1bHQoc3Ry
dWN0IHJwY190YXNrICp0YXNrKQ0KPiAgCQlyZXR1cm47DQo+ICAJY2FzZSAtRVRJTUVET1VUOg0K
PiAgCQlycGNfZGVsYXkodGFzaywgMypIWik7DQo+IC0JY2FzZSAtRUtFWUVYUElSRUQ6DQo+ICAJ
Y2FzZSAtRUFHQUlOOg0KPiAgCQlzdGF0dXMgPSAtRUFDQ0VTOw0KPiArCWNhc2UgLUVLRVlFWFBJ
UkVEOg0KPiAgCQlpZiAoIXRhc2stPnRrX2NyZWRfcmV0cnkpDQo+ICAJCQlicmVhazsNCj4gIAkJ
dGFzay0+dGtfY3JlZF9yZXRyeS0tOw0KDQpBcHBsaWVkLg0KDQotLSANClRyb25kIE15a2xlYnVz
dA0KTGludXggTkZTIGNsaWVudCBtYWludGFpbmVyDQoNCk5ldEFwcA0KVHJvbmQuTXlrbGVidXN0
QG5ldGFwcC5jb20NCnd3dy5uZXRhcHAuY29tDQo=
T24gV2VkLCAyMDEzLTA4LTE0IGF0IDExOjU5IC0wNDAwLCBhbmRyb3NAbmV0YXBwLmNvbSB3cm90
ZToNCj4gRnJvbTogQW5keSBBZGFtc29uIDxhbmRyb3NAbmV0YXBwLmNvbT4NCj4gDQo+IFRoaXMg
cGF0Y2ggcHJvdmlkZXMgdGhlIFJQQyBsYXllciBoZWxwZXIgZnVuY3Rpb25zIHRvIGFsbG93IE5G
UyB0byBtYW5hZ2UNCj4gZGF0YSBpbiB0aGUgZmFjZSBvZiBleHBpcmVkIGNyZWRlbnRpYWxzIC0g
c3VjaCBhcyBhdm9pZGluZyBidWZmZXJlZCBXUklURXMNCj4gYW5kIENPTU1JVHMgd2hlbiB0aGUg
Z3NzIGNvbnRleHQgd2lsbCBleHBpcmUgYmVmb3JlIHRoZSBXUklURXMgYXJlIGZsdXNoZWQNCj4g
YW5kIENPTU1JVHMgYXJlIHNlbnQuDQo+IA0KPiBUaGVzZSBoZWxwZXIgZnVuY3Rpb25zIGVuYWJs
ZSBjaGVja2luZyB0aGUgZXhwaXJhdGlvbiBvZiBhbiB1bmRlcmx5aW5nDQo+IGNyZWRlbnRpYWwg
a2V5IGZvciBhIGdlbmVyaWMgcnBjIGNyZWRlbnRpYWwsIGUuZy4gdGhlIGdzc19jcmVkIGdzcyBj
b250ZXh0DQo+IGdjX2V4cGlyeSB3aGljaCBmb3IgS2VyYmVyb3MgaXMgc2V0IHRvIHRoZSByZW1h
aW5pbmcgVEdUIGxpZmV0aW1lLg0KPiANCj4gQSBuZXcgcnBjX2F1dGhvcHMga2V5X3RpbWVvdXQg
aXMgb25seSBkZWZpbmVkIGZvciB0aGUgZ2VuZXJpYyBhdXRoLg0KPiBBIG5ldyBycGNfY3JlZG9w
cyBjcmtleV90b19leHBpcmUgaXMgb25seSBkZWZpbmVkIGZvciB0aGUgZ2VuZXJpYyBjcmVkLg0K
PiBBIG5ldyBycGNfY3JlZG9wcyBjcmtleV90aW1lb3V0IGlzIG9ubHkgZGVmaW5lZCBmb3IgdGhl
IGdzcyBjcmVkLg0KPiANCj4gU2V0IGEgY3JlZGVudGlhbCBrZXkgZXhwaXJ5IHdhdGVybWFyaywg
UlBDX0tFWV9FWFBJUkVfVElNRU8gc2V0IHRvIDI0MCBzZWNvbmRzDQo+IGFzIGEgZGVmYXVsdCBh
bmQgY2FuIGJlIHNldCB2aWEgYSBtb2R1bGUgcGFyYW1ldGVyIGFzIHdlIG5lZWQgdG8gZW5zdXJl
IHRoZXJlDQo+IGlzIHRpbWUgZm9yIGFueSBkaXJ0eSBkYXRhIHRvIGJlIGZsdXNoZWQuDQo+IA0K
PiBJZiBrZXlfdGltZW91dCBpcyBjYWxsZWQgb24gYSBjcmVkZW50aWFsIHdpdGggYW4gdW5kZXJs
eWluZyBjcmVkZW50aWFsIGtleSB0aGF0DQo+IHdpbGwgZXhwaXJlIHdpdGhpbiB3YXRlcm1hcmsg
c2Vjb25kcywgd2Ugc2V0IHRoZSBSUENfQ1JFRF9LRVlfRVhQSVJFX1NPT04NCj4gZmxhZyBpbiB0
aGUgZ2VuZXJpY19jcmVkIGFjcmVkIHNvIHRoYXQgdGhlIE5GUyBsYXllciBjYW4gY2xlYW4gdXAg
cHJpb3IgdG8NCj4ga2V5IGV4cGlyYXRpb24uDQo+IA0KPiBDaGVja2luZyBhIGdlbmVyaWMgY3Jl
ZGVudGlhbCdzIHVuZGVybHlpbmcgY3JlZGVudGlhbCBpbnZvbHZlcyBhIGNyZWQgbG9va3VwLg0K
PiBUbyBhdm9pZCB0aGlzIGxvb2t1cCBpbiB0aGUgbm9ybWFsIGNhc2Ugd2hlbiB0aGUgdW5kZXJs
eWluZyBjcmVkZW50aWFsIGhhcw0KPiBhIGtleSB0aGF0IGlzIHZhbGlkIChiZWZvcmUgdGhlIHdh
dGVybWFyayksIGEgbm90aWZ5IGZsYWcgaXMgc2V0IGluDQo+IHRoZSBnZW5lcmljIGNyZWRlbnRp
YWwgdGhlIGZpcnN0IHRpbWUgdGhlIGtleV90aW1lb3V0IGlzIGNhbGxlZC4gVGhlDQo+IGdlbmVy
aWMgY3JlZGVudGlhbCB0aGVuIHN0b3BzIGNoZWNraW5nIHRoZSB1bmRlcmx5aW5nIGNyZWRlbnRp
YWwga2V5IGV4cGlyeSwgYW5kDQo+IHRoZSB1bmRlcmx5aW5nIGNyZWRlbnRpYWwgKGdzc19jcmVk
KSBtYXRjaCByb3V0aW5lIHRoZW4gY2hlY2tzIHRoZSBrZXkNCj4gZXhwaXJhdGlvbiB1cG9uIGVh
Y2ggbm9ybWFsIHVzZSBhbmQgc2V0cyBhIGZsYWcgaW4gdGhlIGFzc29jaWF0ZWQgZ2VuZXJpYw0K
PiBjcmVkZW50aWFsIG9ubHkgd2hlbiB0aGUga2V5IGV4cGlyYXRpb24gaXMgd2l0aGluIHRoZSB3
YXRlcm1hcmsuDQo+IFRoaXMgaW4gdHVybiBzaWduYWxzIHRoZSBnZW5lcmljIGNyZWRlbnRpYWwg
a2V5X3RpbWVvdXQgdG8gcGVyZm9ybSB0aGUgZXh0cmENCj4gY3JlZGVudGlhbCBsb29rdXAgdGhl
cmVhZnRlci4NCj4gDQo+IFNpZ25lZC1vZmYtYnk6IEFuZHkgQWRhbXNvbiA8YW5kcm9zQG5ldGFw
cC5jb20+DQo+IC0tLQ0KDQpBcHBsaWVkLg0KDQotLSANClRyb25kIE15a2xlYnVzdA0KTGludXgg
TkZTIGNsaWVudCBtYWludGFpbmVyDQoNCk5ldEFwcA0KVHJvbmQuTXlrbGVidXN0QG5ldGFwcC5j
b20NCnd3dy5uZXRhcHAuY29tDQo=
T24gV2VkLCAyMDEzLTA4LTE0IGF0IDExOjU5IC0wNDAwLCBhbmRyb3NAbmV0YXBwLmNvbSB3cm90
ZToNCj4gRnJvbTogQW5keSBBZGFtc29uIDxhbmRyb3NAbmV0YXBwLmNvbT4NCj4gDQo+IFNpZ25l
ZC1vZmYtYnk6IEFuZHkgQWRhbXNvbiA8YW5kcm9zQG5ldGFwcC5jb20+DQo+IC0tLQ0KPiAgZnMv
bmZzL3dyaXRlLmMgfCAxMSArKysrKysrKysrKw0KPiAgMSBmaWxlIGNoYW5nZWQsIDExIGluc2Vy
dGlvbnMoKykNCj4gDQo+IGRpZmYgLS1naXQgYS9mcy9uZnMvd3JpdGUuYyBiL2ZzL25mcy93cml0
ZS5jDQo+IGluZGV4IGYxYmRiNzIuLjdiN2QzNjAgMTAwNjQ0DQo+IC0tLSBhL2ZzL25mcy93cml0
ZS5jDQo+ICsrKyBiL2ZzL25mcy93cml0ZS5jDQo+IEBAIC0xMzMwLDYgKzEzMzAsMTQgQEAgdm9p
ZCBuZnNfd3JpdGViYWNrX2RvbmUoc3RydWN0IHJwY190YXNrICp0YXNrLCBzdHJ1Y3QgbmZzX3dy
aXRlX2RhdGEgKmRhdGEpDQo+ICAJZHByaW50aygiTkZTOiAlNXUgbmZzX3dyaXRlYmFja19kb25l
IChzdGF0dXMgJWQpXG4iLA0KPiAgCQl0YXNrLT50a19waWQsIHRhc2stPnRrX3N0YXR1cyk7DQo+
ICANCj4gKwlpZiAodGFzay0+dGtfc3RhdHVzID09IC1FS0VZRVhQSVJFRCkNCj4gKwkJcHJfd2Fy
bl9yYXRlbGltaXRlZCgiTkZTOiAgICAgICAgd3JpdGUgYXR0ZW1wdCB3aXRoIGV4cGlyZWQgIg0K
PiArCQkJImNyZWRlbnRpYWwgcmVxICVzLyVsbGQsICV1IGJ5dGVzIEAgb2Zmc2V0ICVsbHVcbiIs
DQo+ICsJCQlpbm9kZS0+aV9zYi0+c19pZCwNCj4gKwkJCShsb25nIGxvbmcpTkZTX0ZJTEVJRChp
bm9kZSksDQo+ICsJCQlkYXRhLT5hcmdzLmNvdW50LA0KPiArCQkJKHVuc2lnbmVkIGxvbmcgbG9u
ZylkYXRhLT5hcmdzLm9mZnNldCk7DQo+ICsNCj4gIAkvKg0KPiAgCSAqIC0+d3JpdGVfZG9uZSB3
aWxsIGF0dGVtcHQgdG8gdXNlIHBvc3Qtb3AgYXR0cmlidXRlcyB0byBkZXRlY3QNCj4gIAkgKiBj
b25mbGljdGluZyB3cml0ZXMgYnkgb3RoZXIgY2xpZW50cy4gIEEgc3RyaWN0IGludGVycHJldGF0
aW9uDQo+IEBAIC0xNTU2LDYgKzE1NjQsOSBAQCBzdGF0aWMgdm9pZCBuZnNfY29tbWl0X2RvbmUo
c3RydWN0IHJwY190YXNrICp0YXNrLCB2b2lkICpjYWxsZGF0YSkNCj4gIA0KPiAgICAgICAgICBk
cHJpbnRrKCJORlM6ICU1dSBuZnNfY29tbWl0X2RvbmUgKHN0YXR1cyAlZClcbiIsDQo+ICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhc2stPnRrX3BpZCwgdGFzay0+dGtfc3RhdHVz
KTsNCj4gKwlpZiAodGFzay0+dGtfc3RhdHVzID09IC1FS0VZRVhQSVJFRCkNCj4gKwkJcHJfd2Fy
bl9yYXRlbGltaXRlZCgiTkZTOiAgICAgICBjb21taXQgYXR0ZW1wdCB3aXRoIGV4cGlyZWQgIg0K
PiArCQkJCSJjcmVkZW50aWFsXG4iKTsNCj4gIA0KPiAgCS8qIENhbGwgdGhlIE5GUyB2ZXJzaW9u
LXNwZWNpZmljIGNvZGUgKi8NCj4gIAlORlNfUFJPVE8oZGF0YS0+aW5vZGUpLT5jb21taXRfZG9u
ZSh0YXNrLCBkYXRhKTsNCg0KSSB3YW50IHRvIGhvbGQgb24gYXBwbHlpbmcgdGhpcyB1bnRpbCBp
dCBpcyBjbGVhciB0aGF0IHdlIG5lZWQgaXQuIEl0DQppc24ndCBvYnZpb3VzIHRvIG1lIHdoeSB3
ZSBuZWVkIHRvIGxvZyB0aGlzIGluZm9ybWF0aW9uIHRvIHRoZSBjb25zb2xlDQppZiB3ZSdyZSBh
bHJlYWR5IHJldHVybmluZyBlcnJvciB2YWx1ZXMgdG8gdGhlIGFwcGxpY2F0aW9uLg0KDQotLSAN
ClRyb25kIE15a2xlYnVzdA0KTGludXggTkZTIGNsaWVudCBtYWludGFpbmVyDQoNCk5ldEFwcA0K
VHJvbmQuTXlrbGVidXN0QG5ldGFwcC5jb20NCnd3dy5uZXRhcHAuY29tDQo=