Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolai Stange <nstange@suse.de>2019-01-14 15:44:03 +0100
committerMiroslav Benes <mbenes@suse.cz>2019-01-17 13:05:53 +0100
commit937b6aa5ae28576d8455d28c0994125ae4995532 (patch)
tree4d002d79d07b3c21e9f53e6d74e724a01b073ec4
parent76fa1a42d05db3cb21e3fbc0b5ccece8dee12f7e (diff)
Fix for CVE-2018-16884 ("nfs4: use-after-free in svc_process_common()")
Live patch for CVE-2018-16884. Upstream commits b8be5674fa9a ("sunrpc: use SVC_NET() in svcauth_gss_* functions") and d4b09acf924b ("sunrpc: use-after-free in svc_process_common()"). KLP: CVE-2018-16884 References: bsc#1119947 CVE-2018-16884 Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Miroslav Benes <mbenes@suse.cz>
-rw-r--r--bsc1119947/livepatch_bsc1119947.c114
-rw-r--r--bsc1119947/livepatch_bsc1119947.h18
-rw-r--r--bsc1119947/livepatch_bsc1119947_auth_rpcgss.c1204
-rw-r--r--bsc1119947/livepatch_bsc1119947_auth_rpcgss.h13
-rw-r--r--bsc1119947/livepatch_bsc1119947_nfsv4.c617
-rw-r--r--bsc1119947/livepatch_bsc1119947_nfsv4.h13
-rw-r--r--bsc1119947/livepatch_bsc1119947_sunrpc.c498
-rw-r--r--bsc1119947/livepatch_bsc1119947_sunrpc.h15
-rw-r--r--bsc1119947/patched_funcs.csv4
9 files changed, 2496 insertions, 0 deletions
diff --git a/bsc1119947/livepatch_bsc1119947.c b/bsc1119947/livepatch_bsc1119947.c
new file mode 100644
index 0000000..90ea484
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947.c
@@ -0,0 +1,114 @@
+/*
+ * livepatch_bsc1119947
+ *
+ * Fix for CVE-2018-16884, bsc#1119947
+ *
+ * Upstream commits:
+ * b8be5674fa9a ("sunrpc: use SVC_NET() in svcauth_gss_* functions")
+ * d4b09acf924b ("sunrpc: use-after-free in svc_process_common()")
+ *
+ * SLE12(-SP1) commits:
+ * 3f13d98021aee924d079e994da959d713588cc1d
+ * cc1b1eb412289fc937541c4bf957d9696711c083
+ *
+ * SLE12-SP2 and -SP3 commits:
+ * 6f61e427375e9951341927e7c4e9d379e98ed7c1
+ * a9586683776348bca822ad33942d095cd353c1b5
+ *
+ * SLE15 commit:
+ * 1ac740512f78f5b140fdf408fc4d95384b19d06f
+ * eae94a95abdf35c8a5c5afa1d18d712cb8c1bfac
+ *
+ *
+ * Copyright (c) 2019 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sunrpc/svc_xprt.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/livepatch.h>
+#include "shadow.h"
+#include "livepatch_bsc1119947.h"
+
+#define KLP_SHADOW_RQ_BC_NET_ID KLP_SHADOW_ID(1119947, 1)
+
+
+/* Patched SVC_NET() macro. */
+struct net *klp_svc_net(struct svc_rqst *rqstp)
+{
+ struct net **rq_bc_net = klp_shadow_get(rqstp, KLP_SHADOW_RQ_BC_NET_ID);
+
+ if (rq_bc_net)
+ return *rq_bc_net;
+
+ return rqstp->rq_xprt->xpt_net;
+}
+
+void klp_shadow_rq_bc_net_set(struct svc_rqst *rqstp, struct net *net)
+{
+ struct net **rq_bc_net;
+
+ rq_bc_net = klp_shadow_alloc(rqstp, KLP_SHADOW_RQ_BC_NET_ID,
+ sizeof(*rq_bc_net), GFP_KERNEL,
+ NULL, NULL);
+ if (!rq_bc_net) {
+ /* Don't pass failure to caller. */
+ return;
+ }
+
+ *rq_bc_net = net;
+}
+
+void klp_shadow_rq_bc_net_destroy(struct svc_rqst *rqstp)
+{
+ klp_shadow_free(rqstp, KLP_SHADOW_RQ_BC_NET_ID, NULL);
+}
+
+
+int livepatch_bsc1119947_init(void)
+{
+ int ret;
+
+ ret = livepatch_bsc1119947_sunrpc_init();
+ if (ret)
+ return ret;
+
+ ret = livepatch_bsc1119947_auth_rpcgss_init();
+ if (ret) {
+ livepatch_bsc1119947_sunrpc_cleanup();
+ return ret;
+ }
+
+ ret = livepatch_bsc1119947_nfsv4_init();
+ if (ret) {
+ livepatch_bsc1119947_auth_rpcgss_cleanup();
+ livepatch_bsc1119947_sunrpc_cleanup();
+ return ret;
+ }
+
+ return 0;
+}
+
+void livepatch_bsc1119947_cleanup(void)
+{
+ livepatch_bsc1119947_nfsv4_cleanup();
+ livepatch_bsc1119947_auth_rpcgss_cleanup();
+ livepatch_bsc1119947_sunrpc_cleanup();
+}
diff --git a/bsc1119947/livepatch_bsc1119947.h b/bsc1119947/livepatch_bsc1119947.h
new file mode 100644
index 0000000..b21a52b
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947.h
@@ -0,0 +1,18 @@
+#ifndef _LIVEPATCH_BSC1119947_H
+#define _LIVEPATCH_BSC1119947_H
+
+#include "livepatch_bsc1119947_sunrpc.h"
+#include "livepatch_bsc1119947_auth_rpcgss.h"
+#include "livepatch_bsc1119947_nfsv4.h"
+
+int livepatch_bsc1119947_init(void);
+void livepatch_bsc1119947_cleanup(void);
+
+
+struct svc_rqst;
+
+struct net *klp_svc_net(struct svc_rqst *rqstp);
+void klp_shadow_rq_bc_net_set(struct svc_rqst *rqstp, struct net *net);
+void klp_shadow_rq_bc_net_destroy(struct svc_rqst *rqstp);
+
+#endif /* _LIVEPATCH_BSC1119947_H */
diff --git a/bsc1119947/livepatch_bsc1119947_auth_rpcgss.c b/bsc1119947/livepatch_bsc1119947_auth_rpcgss.c
new file mode 100644
index 0000000..1e6a665
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947_auth_rpcgss.c
@@ -0,0 +1,1204 @@
+/*
+ * livepatch_bsc1119947_auth_rpcgss
+ *
+ * Fix for CVE-2018-16884, bsc#1119947 -- auth_rpcgss.ko part
+ *
+ * Copyright (c) 2019 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/sunrpc/auth_gss.h>
+#include <linux/sunrpc/cache.h>
+#include <linux/sunrpc/gss_api.h>
+#include <linux/sunrpc/gss_err.h>
+#include <linux/sunrpc/debug.h>
+#include <net/netns/generic.h>
+#include "livepatch_bsc1119947.h"
+#include "kallsyms_relocs.h"
+
+#if !IS_MODULE(CONFIG_SUNRPC_GSS)
+#error "Live patch supports only CONFIG_SUNRPC_GSS=m"
+#endif
+
+#if !IS_MODULE(CONFIG_SUNRPC)
+#error "Live patch supports only CONFIG_SUNRPC=m"
+#endif
+
+#if !IS_ENABLED(CONFIG_SUNRPC_DEBUG)
+#error "Live patch supports only CONFIG_SUNRPC_DEBUG=y"
+#endif
+
+
+#define KLP_PATCHED_MODULE "auth_rpcgss"
+
+
+static unsigned int *klp_rpc_debug;
+static unsigned int *klp_sunrpc_net_id;
+
+struct rsi;
+struct gssp_upcall_data;
+
+static struct cache_head *
+(*klp_sunrpc_cache_lookup)(struct cache_detail *detail,
+ struct cache_head *key, int hash);
+static int (*klp_cache_check)(struct cache_detail *detail,
+ struct cache_head *h,
+ struct cache_req *rqstp);
+static void (*klp_sunrpc_cache_unhash)(struct cache_detail *cd,
+ struct cache_head *h);
+static void (*klp_xdr_buf_from_iov)(struct kvec *iov, struct xdr_buf *buf);
+
+static int (*klp_xdr_buf_subsegment)(struct xdr_buf *buf,
+ struct xdr_buf *subbuf,
+ unsigned int base, unsigned int len);
+static void (*klp_xdr_buf_trim)(struct xdr_buf *buf, unsigned int len);
+static int (*klp_read_bytes_from_xdr_buf)(struct xdr_buf *buf,
+ unsigned int base, void *obj,
+ unsigned int len);
+static void (*klp_xdr_shift_buf)(struct xdr_buf *buf, size_t len);
+static void (*klp_auth_domain_put)(struct auth_domain *dom);
+static void (*klp_rsi_free)(struct rsi *rsii);
+static int (*klp_gssp_accept_sec_context_upcall)(struct net *net,
+ struct gssp_upcall_data *data);
+static int (*klp_gss_proxy_save_rsc)(struct cache_detail *cd,
+ struct gssp_upcall_data *ud,
+ uint64_t *handle);
+static void (*klp_gssp_free_upcall_data)(struct gssp_upcall_data *data);
+static int (*klp_set_gss_proxy)(struct net *net, int type);
+static struct rsc *(*klp_gss_svc_searchbyctx)(struct cache_detail *cd,
+ struct xdr_netobj *handle);
+static u32 (*klp_gss_verify_mic)(struct gss_ctx *context_handle,
+ struct xdr_buf *message,
+ struct xdr_netobj *mic_token);
+static int (*klp_gss_write_verf)(struct svc_rqst *rqstp, struct gss_ctx *ctx_id,
+ u32 seq);
+static u32 (*klp_gss_wrap)(struct gss_ctx *ctx_id, int offset,
+ struct xdr_buf *buf, struct page **inpages);
+static u32 (*klp_gss_unwrap)(struct gss_ctx *ctx_id, int offset,
+ struct xdr_buf *buf);
+static rpc_authflavor_t
+(*klp_gss_svc_to_pseudoflavor)(struct gss_api_mech *gm, u32 qop, u32 service);
+static u32 (*klp_gss_get_mic)(struct gss_ctx *context_handle,
+ struct xdr_buf *message,
+ struct xdr_netobj *mic_token);
+
+static struct klp_kallsyms_reloc klp_funcs[] = {
+ { "rpc_debug", (void *)&klp_rpc_debug, "sunrpc" },
+ { "sunrpc_net_id", (void *)&klp_sunrpc_net_id, "sunrpc" },
+ { "sunrpc_cache_lookup", (void *)&klp_sunrpc_cache_lookup, "sunrpc" },
+ { "cache_check", (void *)&klp_cache_check, "sunrpc" },
+ { "sunrpc_cache_unhash", (void *)&klp_sunrpc_cache_unhash, "sunrpc" },
+ { "xdr_buf_from_iov", (void *)&klp_xdr_buf_from_iov, "sunrpc" },
+ { "xdr_buf_subsegment", (void *)&klp_xdr_buf_subsegment, "sunrpc" },
+ { "xdr_buf_trim", (void *)&klp_xdr_buf_trim, "sunrpc" },
+ { "read_bytes_from_xdr_buf", (void *)&klp_read_bytes_from_xdr_buf,
+ "sunrpc" },
+ { "xdr_shift_buf", (void *)&klp_xdr_shift_buf, "sunrpc" },
+ { "auth_domain_put", (void *)&klp_auth_domain_put, "sunrpc" },
+ { "rsi_free", (void *)&klp_rsi_free, "auth_rpcgss" },
+ { "gssp_accept_sec_context_upcall",
+ (void *)&klp_gssp_accept_sec_context_upcall, "auth_rpcgss" },
+ { "gss_proxy_save_rsc", (void *)&klp_gss_proxy_save_rsc,
+ "auth_rpcgss" },
+ { "gssp_free_upcall_data", (void *)&klp_gssp_free_upcall_data,
+ "auth_rpcgss" },
+ { "set_gss_proxy", (void *)&klp_set_gss_proxy, "auth_rpcgss" },
+ { "gss_svc_searchbyctx", (void *)&klp_gss_svc_searchbyctx,
+ "auth_rpcgss" },
+ { "gss_verify_mic", (void *)&klp_gss_verify_mic, "auth_rpcgss" },
+ { "gss_write_verf", (void *)&klp_gss_write_verf, "auth_rpcgss" },
+ { "gss_wrap", (void *)&klp_gss_wrap, "auth_rpcgss" },
+ { "gss_unwrap", (void *)&klp_gss_unwrap, "auth_rpcgss" },
+ { "gss_svc_to_pseudoflavor", (void *)&klp_gss_svc_to_pseudoflavor,
+ "auth_rpcgss" },
+ { "gss_get_mic", (void *)&klp_gss_get_mic, "auth_rpcgss" },
+};
+
+
+
+/* from include/linux/sunrpc/debug.h */
+/* resolve rpc_debug */
+#undef ifdebug
+# define ifdebug(fac) if (unlikely((*klp_rpc_debug) & RPCDBG_##fac))
+
+
+/* from net/sunrpc/netns.h */
+struct sunrpc_net {
+ struct proc_dir_entry *proc_net_rpc;
+ struct cache_detail *ip_map_cache;
+ struct cache_detail *unix_gid_cache;
+ struct cache_detail *rsc_cache;
+ struct cache_detail *rsi_cache;
+
+ struct super_block *pipefs_sb;
+ struct rpc_pipe *gssd_dummy;
+ struct mutex pipefs_sb_lock;
+
+ struct list_head all_clients;
+ spinlock_t rpc_client_lock;
+
+ struct rpc_clnt *rpcb_local_clnt;
+ struct rpc_clnt *rpcb_local_clnt4;
+ spinlock_t rpcb_clnt_lock;
+ unsigned int rpcb_users;
+ unsigned int rpcb_is_af_local : 1;
+
+ struct mutex gssp_lock;
+ struct rpc_clnt *gssp_clnt;
+ int use_gss_proxy;
+ int pipe_version;
+ atomic_t pipe_users;
+ struct proc_dir_entry *use_gssp_proc;
+};
+
+
+/* from net/sunrpc/auth_gss/gss_rpc_xdr.h */
+struct gssp_in_token {
+ struct page **pages; /* Array of contiguous pages */
+ unsigned int page_base; /* Start of page data */
+ unsigned int page_len; /* Length of page data */
+};
+
+
+/* from net/sunrpc/auth_gss/gss_rpc_upcall.h */
+struct gssp_upcall_data {
+ struct xdr_netobj in_handle;
+ struct gssp_in_token in_token;
+ struct xdr_netobj out_handle;
+ struct xdr_netobj out_token;
+ struct rpcsec_gss_oid mech_oid;
+ struct svc_cred creds;
+ int found_creds;
+ int major_status;
+ int minor_status;
+};
+
+
+/* from net/sunrpc/auth_gss/svcauth_gss.c */
+# define RPCDBG_FACILITY RPCDBG_AUTH
+
+#define KLP_RSI_HASHBITS 6
+
+struct rsi {
+ struct cache_head h;
+ struct xdr_netobj in_handle, in_token;
+ struct xdr_netobj out_handle, out_token;
+ int major_status, minor_status;
+};
+
+/* inlined */
+static inline int klp_rsi_hash(struct rsi *item)
+{
+ return hash_mem(item->in_handle.data, item->in_handle.len, KLP_RSI_HASHBITS)
+ ^ hash_mem(item->in_token.data, item->in_token.len, KLP_RSI_HASHBITS);
+}
+
+/* inlined */
+static int klp_dup_to_netobj(struct xdr_netobj *dst, char *src, int len)
+{
+ dst->len = len;
+ dst->data = (len ? kmemdup(src, len, GFP_KERNEL) : NULL);
+ if (len && !dst->data)
+ return -ENOMEM;
+ return 0;
+}
+
+/* inlined */
+static inline int klp_dup_netobj(struct xdr_netobj *dst, struct xdr_netobj *src)
+{
+ return klp_dup_to_netobj(dst, src->data, src->len);
+}
+
+/* inlined */
+static struct rsi *klp_rsi_lookup(struct cache_detail *cd, struct rsi *item)
+{
+ struct cache_head *ch;
+ int hash = klp_rsi_hash(item);
+
+ ch = klp_sunrpc_cache_lookup(cd, &item->h, hash);
+ if (ch)
+ return container_of(ch, struct rsi, h);
+ else
+ return NULL;
+}
+
+#define KLP_GSS_SEQ_WIN 128
+
+struct gss_svc_seq_data {
+ /* highest seq number seen so far: */
+ int sd_max;
+ /* for i such that sd_max-GSS_SEQ_WIN < i <= sd_max, the i-th bit of
+ * sd_win is nonzero iff sequence number i has been seen already: */
+ unsigned long sd_win[KLP_GSS_SEQ_WIN/BITS_PER_LONG];
+ spinlock_t sd_lock;
+};
+
+struct rsc {
+ struct cache_head h;
+ struct xdr_netobj handle;
+ struct svc_cred cred;
+ struct gss_svc_seq_data seqdata;
+ struct gss_ctx *mechctx;
+};
+
+/* inlined */
+static int
+klp_gss_check_seq_num(struct rsc *rsci, int seq_num)
+{
+ struct gss_svc_seq_data *sd = &rsci->seqdata;
+
+ spin_lock(&sd->sd_lock);
+ if (seq_num > sd->sd_max) {
+ if (seq_num >= sd->sd_max + KLP_GSS_SEQ_WIN) {
+ memset(sd->sd_win,0,sizeof(sd->sd_win));
+ sd->sd_max = seq_num;
+ } else while (sd->sd_max < seq_num) {
+ sd->sd_max++;
+ __clear_bit(sd->sd_max % KLP_GSS_SEQ_WIN, sd->sd_win);
+ }
+ __set_bit(seq_num % KLP_GSS_SEQ_WIN, sd->sd_win);
+ goto ok;
+ } else if (seq_num <= sd->sd_max - KLP_GSS_SEQ_WIN) {
+ goto drop;
+ }
+ /* sd_max - GSS_SEQ_WIN < seq_num <= sd_max */
+ if (__test_and_set_bit(seq_num % KLP_GSS_SEQ_WIN, sd->sd_win))
+ goto drop;
+ok:
+ spin_unlock(&sd->sd_lock);
+ return 1;
+drop:
+ spin_unlock(&sd->sd_lock);
+ return 0;
+}
+
+/* inlined */
+static inline u32 klp_round_up_to_quad(u32 i)
+{
+ return (i + 3 ) & ~3;
+}
+
+/* inlined */
+static inline int
+klp_svc_safe_getnetobj(struct kvec *argv, struct xdr_netobj *o)
+{
+ int l;
+
+ if (argv->iov_len < 4)
+ return -1;
+ o->len = svc_getnl(argv);
+ l = klp_round_up_to_quad(o->len);
+ if (argv->iov_len < l)
+ return -1;
+ o->data = argv->iov_base;
+ argv->iov_base += l;
+ argv->iov_len -= l;
+ return 0;
+}
+
+/* inlined */
+static inline int
+klp_svc_safe_putnetobj(struct kvec *resv, struct xdr_netobj *o)
+{
+ u8 *p;
+
+ if (resv->iov_len + 4 > PAGE_SIZE)
+ return -1;
+ svc_putnl(resv, o->len);
+ p = resv->iov_base + resv->iov_len;
+ resv->iov_len += klp_round_up_to_quad(o->len);
+ if (resv->iov_len > PAGE_SIZE)
+ return -1;
+ memcpy(p, o->data, o->len);
+ memset(p + o->len, 0, klp_round_up_to_quad(o->len) - o->len);
+ return 0;
+}
+
+/* inlined */
+static int
+klp_gss_verify_header(struct svc_rqst *rqstp, struct rsc *rsci,
+ __be32 *rpcstart, struct rpc_gss_wire_cred *gc,
+ __be32 *authp)
+{
+ struct gss_ctx *ctx_id = rsci->mechctx;
+ struct xdr_buf rpchdr;
+ struct xdr_netobj checksum;
+ u32 flavor = 0;
+ struct kvec *argv = &rqstp->rq_arg.head[0];
+ struct kvec iov;
+
+ /* data to compute the checksum over: */
+ iov.iov_base = rpcstart;
+ iov.iov_len = (u8 *)argv->iov_base - (u8 *)rpcstart;
+ klp_xdr_buf_from_iov(&iov, &rpchdr);
+
+ *authp = rpc_autherr_badverf;
+ if (argv->iov_len < 4)
+ return SVC_DENIED;
+ flavor = svc_getnl(argv);
+ if (flavor != RPC_AUTH_GSS)
+ return SVC_DENIED;
+ if (klp_svc_safe_getnetobj(argv, &checksum))
+ return SVC_DENIED;
+
+ if (rqstp->rq_deferred) /* skip verification of revisited request */
+ return SVC_OK;
+ if (klp_gss_verify_mic(ctx_id, &rpchdr, &checksum) != GSS_S_COMPLETE) {
+ *authp = rpcsec_gsserr_credproblem;
+ return SVC_DENIED;
+ }
+
+ if (gc->gc_seq > MAXSEQ) {
+ dprintk("RPC: svcauth_gss: discarding request with "
+ "large sequence number %d\n", gc->gc_seq);
+ *authp = rpcsec_gsserr_ctxproblem;
+ return SVC_DENIED;
+ }
+ if (!klp_gss_check_seq_num(rsci, gc->gc_seq)) {
+ dprintk("RPC: svcauth_gss: discarding request with "
+ "old sequence number %d\n", gc->gc_seq);
+ return SVC_DROP;
+ }
+ return SVC_OK;
+}
+
+/* inlined */
+static int
+klp_gss_write_null_verf(struct svc_rqst *rqstp)
+{
+ __be32 *p;
+
+ svc_putnl(rqstp->rq_res.head, RPC_AUTH_NULL);
+ p = rqstp->rq_res.head->iov_base + rqstp->rq_res.head->iov_len;
+ /* don't really need to check if head->iov_len > PAGE_SIZE ... */
+ *p++ = 0;
+ if (!xdr_ressize_check(rqstp, p))
+ return -1;
+ return 0;
+}
+
+/* inlined */
+static inline int
+klp_read_u32_from_xdr_buf(struct xdr_buf *buf, int base, u32 *obj)
+{
+ __be32 raw;
+ int status;
+
+ status = klp_read_bytes_from_xdr_buf(buf, base, &raw, sizeof(*obj));
+ if (status)
+ return status;
+ *obj = ntohl(raw);
+ return 0;
+}
+
+/* inlined */
+static int
+klp_unwrap_integ_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq,
+ struct gss_ctx *ctx)
+{
+ int stat = -EINVAL;
+ u32 integ_len, maj_stat;
+ struct xdr_netobj mic;
+ struct xdr_buf integ_buf;
+
+ /* Did we already verify the signature on the original pass through? */
+ if (rqstp->rq_deferred)
+ return 0;
+
+ integ_len = svc_getnl(&buf->head[0]);
+ if (integ_len & 3)
+ return stat;
+ if (integ_len > buf->len)
+ return stat;
+ if (klp_xdr_buf_subsegment(buf, &integ_buf, 0, integ_len))
+ BUG();
+ /* copy out mic... */
+ if (klp_read_u32_from_xdr_buf(buf, integ_len, &mic.len))
+ BUG();
+ if (mic.len > RPC_MAX_AUTH_SIZE)
+ return stat;
+ mic.data = kmalloc(mic.len, GFP_KERNEL);
+ if (!mic.data)
+ return stat;
+ if (klp_read_bytes_from_xdr_buf(buf, integ_len + 4, mic.data, mic.len))
+ goto out;
+ maj_stat = klp_gss_verify_mic(ctx, &integ_buf, &mic);
+ if (maj_stat != GSS_S_COMPLETE)
+ goto out;
+ if (svc_getnl(&buf->head[0]) != seq)
+ goto out;
+ /* trim off the mic and padding at the end before returning */
+ klp_xdr_buf_trim(buf, klp_round_up_to_quad(mic.len) + 4);
+ stat = 0;
+out:
+ kfree(mic.data);
+ return stat;
+}
+
+/* inlined */
+static inline int
+klp_total_buf_len(struct xdr_buf *buf)
+{
+ return buf->head[0].iov_len + buf->page_len + buf->tail[0].iov_len;
+}
+
+/* inlined */
+static void
+klp_fix_priv_head(struct xdr_buf *buf, int pad)
+{
+ if (buf->page_len == 0) {
+ /* We need to adjust head and buf->len in tandem in this
+ * case to make svc_defer() work--it finds the original
+ * buffer start using buf->len - buf->head[0].iov_len. */
+ buf->head[0].iov_len -= pad;
+ }
+}
+
+/* inlined */
+static int
+klp_unwrap_priv_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq,
+ struct gss_ctx *ctx)
+{
+ u32 priv_len, maj_stat;
+ int pad, saved_len, remaining_len, offset;
+
+ clear_bit(RQ_SPLICE_OK, &rqstp->rq_flags);
+
+ priv_len = svc_getnl(&buf->head[0]);
+ if (rqstp->rq_deferred) {
+ /* Already decrypted last time through! The sequence number
+ * check at out_seq is unnecessary but harmless: */
+ goto out_seq;
+ }
+ /* buf->len is the number of bytes from the original start of the
+ * request to the end, where head[0].iov_len is just the bytes
+ * not yet read from the head, so these two values are different: */
+ remaining_len = klp_total_buf_len(buf);
+ if (priv_len > remaining_len)
+ return -EINVAL;
+ pad = remaining_len - priv_len;
+ buf->len -= pad;
+ klp_fix_priv_head(buf, pad);
+
+ /* Maybe it would be better to give gss_unwrap a length parameter: */
+ saved_len = buf->len;
+ buf->len = priv_len;
+ maj_stat = klp_gss_unwrap(ctx, 0, buf);
+ pad = priv_len - buf->len;
+ buf->len = saved_len;
+ buf->len -= pad;
+ /* The upper layers assume the buffer is aligned on 4-byte boundaries.
+ * In the krb5p case, at least, the data ends up offset, so we need to
+ * move it around. */
+ /* XXX: This is very inefficient. It would be better to either do
+ * this while we encrypt, or maybe in the receive code, if we can peak
+ * ahead and work out the service and mechanism there. */
+ offset = buf->head[0].iov_len % 4;
+ if (offset) {
+ buf->buflen = RPCSVC_MAXPAYLOAD;
+ klp_xdr_shift_buf(buf, offset);
+ klp_fix_priv_head(buf, pad);
+ }
+ if (maj_stat != GSS_S_COMPLETE)
+ return -EINVAL;
+out_seq:
+ if (svc_getnl(&buf->head[0]) != seq)
+ return -EINVAL;
+ return 0;
+}
+
+struct gss_svc_data {
+ /* decoded gss client cred: */
+ struct rpc_gss_wire_cred clcred;
+ /* save a pointer to the beginning of the encoded verifier,
+ * for use in encryption/checksumming in svcauth_gss_release: */
+ __be32 *verf_start;
+ struct rsc *rsci;
+};
+
+/* inlined */
+static inline int
+klp_gss_write_init_verf(struct cache_detail *cd, struct svc_rqst *rqstp,
+ struct xdr_netobj *out_handle, int *major_status)
+{
+ struct rsc *rsci;
+ int rc;
+
+ if (*major_status != GSS_S_COMPLETE)
+ return klp_gss_write_null_verf(rqstp);
+ rsci = klp_gss_svc_searchbyctx(cd, out_handle);
+ if (rsci == NULL) {
+ *major_status = GSS_S_NO_CONTEXT;
+ return klp_gss_write_null_verf(rqstp);
+ }
+ rc = klp_gss_write_verf(rqstp, rsci->mechctx, KLP_GSS_SEQ_WIN);
+ cache_put(&rsci->h, cd);
+ return rc;
+}
+
+/* inlined */
+static inline int
+klp_gss_read_common_verf(struct rpc_gss_wire_cred *gc,
+ struct kvec *argv, __be32 *authp,
+ struct xdr_netobj *in_handle)
+{
+ /* Read the verifier; should be NULL: */
+ *authp = rpc_autherr_badverf;
+ if (argv->iov_len < 2 * 4)
+ return SVC_DENIED;
+ if (svc_getnl(argv) != RPC_AUTH_NULL)
+ return SVC_DENIED;
+ if (svc_getnl(argv) != 0)
+ return SVC_DENIED;
+ /* Martial context handle and token for upcall: */
+ *authp = rpc_autherr_badcred;
+ if (gc->gc_proc == RPC_GSS_PROC_INIT && gc->gc_ctx.len != 0)
+ return SVC_DENIED;
+ if (klp_dup_netobj(in_handle, &gc->gc_ctx))
+ return SVC_CLOSE;
+ *authp = rpc_autherr_badverf;
+
+ return 0;
+}
+
+/* inlined */
+static inline int
+klp_gss_read_verf(struct rpc_gss_wire_cred *gc,
+ struct kvec *argv, __be32 *authp,
+ struct xdr_netobj *in_handle,
+ struct xdr_netobj *in_token)
+{
+ struct xdr_netobj tmpobj;
+ int res;
+
+ res = klp_gss_read_common_verf(gc, argv, authp, in_handle);
+ if (res)
+ return res;
+
+ if (klp_svc_safe_getnetobj(argv, &tmpobj)) {
+ kfree(in_handle->data);
+ return SVC_DENIED;
+ }
+ if (klp_dup_netobj(in_token, &tmpobj)) {
+ kfree(in_handle->data);
+ return SVC_CLOSE;
+ }
+
+ return 0;
+}
+
+/* inlined */
+static inline int
+klp_gss_read_proxy_verf(struct svc_rqst *rqstp,
+ struct rpc_gss_wire_cred *gc, __be32 *authp,
+ struct xdr_netobj *in_handle,
+ struct gssp_in_token *in_token)
+{
+ struct kvec *argv = &rqstp->rq_arg.head[0];
+ u32 inlen;
+ int res;
+
+ res = klp_gss_read_common_verf(gc, argv, authp, in_handle);
+ if (res)
+ return res;
+
+ inlen = svc_getnl(argv);
+ if (inlen > (argv->iov_len + rqstp->rq_arg.page_len))
+ return SVC_DENIED;
+
+ in_token->pages = rqstp->rq_pages;
+ in_token->page_base = (ulong)argv->iov_base & ~PAGE_MASK;
+ in_token->page_len = inlen;
+
+ return 0;
+}
+
+/* optimized */
+static inline int
+klp_gss_write_resv(struct kvec *resv, size_t size_limit,
+ struct xdr_netobj *out_handle, struct xdr_netobj *out_token,
+ int major_status, int minor_status)
+{
+ if (resv->iov_len + 4 > size_limit)
+ return -1;
+ svc_putnl(resv, RPC_SUCCESS);
+ if (klp_svc_safe_putnetobj(resv, out_handle))
+ return -1;
+ if (resv->iov_len + 3 * 4 > size_limit)
+ return -1;
+ svc_putnl(resv, major_status);
+ svc_putnl(resv, minor_status);
+ svc_putnl(resv, KLP_GSS_SEQ_WIN);
+ if (klp_svc_safe_putnetobj(resv, out_token))
+ return -1;
+ return 0;
+}
+
+/* inlined */
+static bool klp_use_gss_proxy(struct net *net)
+{
+ struct sunrpc_net *sn = net_generic(net, (*klp_sunrpc_net_id));
+
+ /* If use_gss_proxy is still undefined, then try to disable it */
+ if (sn->use_gss_proxy == -1)
+ klp_set_gss_proxy(net, 0);
+ return sn->use_gss_proxy;
+}
+
+/* optimized */
+static __be32 *
+klp_svcauth_gss_prepare_to_wrap(struct xdr_buf *resbuf, struct gss_svc_data *gsd)
+{
+ __be32 *p;
+ u32 verf_len;
+
+ p = gsd->verf_start;
+ gsd->verf_start = NULL;
+
+ /* If the reply stat is nonzero, don't wrap: */
+ if (*(p-1) != rpc_success)
+ return NULL;
+ /* Skip the verifier: */
+ p += 1;
+ verf_len = ntohl(*p++);
+ p += XDR_QUADLEN(verf_len);
+ /* move accept_stat to right place: */
+ memcpy(p, p + 2, 4);
+ /* Also don't wrap if the accept stat is nonzero: */
+ if (*p != rpc_success) {
+ resbuf->head[0].iov_len -= 2 * 4;
+ return NULL;
+ }
+ p++;
+ return p;
+}
+
+/* inlined */
+static inline int
+klp_svcauth_gss_wrap_resp_integ(struct svc_rqst *rqstp)
+{
+ struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data;
+ struct rpc_gss_wire_cred *gc = &gsd->clcred;
+ struct xdr_buf *resbuf = &rqstp->rq_res;
+ struct xdr_buf integ_buf;
+ struct xdr_netobj mic;
+ struct kvec *resv;
+ __be32 *p;
+ int integ_offset, integ_len;
+ int stat = -EINVAL;
+
+ p = klp_svcauth_gss_prepare_to_wrap(resbuf, gsd);
+ if (p == NULL)
+ goto out;
+ integ_offset = (u8 *)(p + 1) - (u8 *)resbuf->head[0].iov_base;
+ integ_len = resbuf->len - integ_offset;
+ BUG_ON(integ_len % 4);
+ *p++ = htonl(integ_len);
+ *p++ = htonl(gc->gc_seq);
+ if (klp_xdr_buf_subsegment(resbuf, &integ_buf, integ_offset, integ_len))
+ BUG();
+ if (resbuf->tail[0].iov_base == NULL) {
+ if (resbuf->head[0].iov_len + RPC_MAX_AUTH_SIZE > PAGE_SIZE)
+ goto out_err;
+ resbuf->tail[0].iov_base = resbuf->head[0].iov_base
+ + resbuf->head[0].iov_len;
+ resbuf->tail[0].iov_len = 0;
+ }
+ resv = &resbuf->tail[0];
+ mic.data = (u8 *)resv->iov_base + resv->iov_len + 4;
+ if (klp_gss_get_mic(gsd->rsci->mechctx, &integ_buf, &mic))
+ goto out_err;
+ svc_putnl(resv, mic.len);
+ memset(mic.data + mic.len, 0,
+ klp_round_up_to_quad(mic.len) - mic.len);
+ resv->iov_len += XDR_QUADLEN(mic.len) << 2;
+ /* not strictly required: */
+ resbuf->len += XDR_QUADLEN(mic.len) << 2;
+ BUG_ON(resv->iov_len > PAGE_SIZE);
+out:
+ stat = 0;
+out_err:
+ return stat;
+}
+
+/* inlined */
+static inline int
+klp_svcauth_gss_wrap_resp_priv(struct svc_rqst *rqstp)
+{
+ struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data;
+ struct rpc_gss_wire_cred *gc = &gsd->clcred;
+ struct xdr_buf *resbuf = &rqstp->rq_res;
+ struct page **inpages = NULL;
+ __be32 *p, *len;
+ int offset;
+ int pad;
+
+ p = klp_svcauth_gss_prepare_to_wrap(resbuf, gsd);
+ if (p == NULL)
+ return 0;
+ len = p++;
+ offset = (u8 *)p - (u8 *)resbuf->head[0].iov_base;
+ *p++ = htonl(gc->gc_seq);
+ inpages = resbuf->pages;
+ /* XXX: Would be better to write some xdr helper functions for
+ * nfs{2,3,4}xdr.c that place the data right, instead of copying: */
+
+ /*
+ * If there is currently tail data, make sure there is
+ * room for the head, tail, and 2 * RPC_MAX_AUTH_SIZE in
+ * the page, and move the current tail data such that
+ * there is RPC_MAX_AUTH_SIZE slack space available in
+ * both the head and tail.
+ */
+ if (resbuf->tail[0].iov_base) {
+ BUG_ON(resbuf->tail[0].iov_base >= resbuf->head[0].iov_base
+ + PAGE_SIZE);
+ BUG_ON(resbuf->tail[0].iov_base < resbuf->head[0].iov_base);
+ if (resbuf->tail[0].iov_len + resbuf->head[0].iov_len
+ + 2 * RPC_MAX_AUTH_SIZE > PAGE_SIZE)
+ return -ENOMEM;
+ memmove(resbuf->tail[0].iov_base + RPC_MAX_AUTH_SIZE,
+ resbuf->tail[0].iov_base,
+ resbuf->tail[0].iov_len);
+ resbuf->tail[0].iov_base += RPC_MAX_AUTH_SIZE;
+ }
+ /*
+ * If there is no current tail data, make sure there is
+ * room for the head data, and 2 * RPC_MAX_AUTH_SIZE in the
+ * allotted page, and set up tail information such that there
+ * is RPC_MAX_AUTH_SIZE slack space available in both the
+ * head and tail.
+ */
+ if (resbuf->tail[0].iov_base == NULL) {
+ if (resbuf->head[0].iov_len + 2*RPC_MAX_AUTH_SIZE > PAGE_SIZE)
+ return -ENOMEM;
+ resbuf->tail[0].iov_base = resbuf->head[0].iov_base
+ + resbuf->head[0].iov_len + RPC_MAX_AUTH_SIZE;
+ resbuf->tail[0].iov_len = 0;
+ }
+ if (klp_gss_wrap(gsd->rsci->mechctx, offset, resbuf, inpages))
+ return -ENOMEM;
+ *len = htonl(resbuf->len - offset);
+ pad = 3 - ((resbuf->len - offset - 1)&3);
+ p = (__be32 *)(resbuf->tail[0].iov_base + resbuf->tail[0].iov_len);
+ memset(p, 0, pad);
+ resbuf->tail[0].iov_len += pad;
+ resbuf->len += pad;
+ return 0;
+}
+
+
+
+/* patched, only caller, svcauth_gss_accept(), also patched. */
+static int klp_svcauth_gss_legacy_init(struct svc_rqst *rqstp,
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +2 lines
+ */
+ struct rpc_gss_wire_cred *gc, __be32 *authp,
+ struct net *net)
+{
+ struct kvec *argv = &rqstp->rq_arg.head[0];
+ struct kvec *resv = &rqstp->rq_res.head[0];
+ struct rsi *rsip, rsikey;
+ int ret;
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ struct sunrpc_net *sn = net_generic(net, (*klp_sunrpc_net_id));
+
+ memset(&rsikey, 0, sizeof(rsikey));
+ ret = klp_gss_read_verf(gc, argv, authp,
+ &rsikey.in_handle, &rsikey.in_token);
+ if (ret)
+ return ret;
+
+ /* Perform upcall, or find upcall result: */
+ rsip = klp_rsi_lookup(sn->rsi_cache, &rsikey);
+ klp_rsi_free(&rsikey);
+ if (!rsip)
+ return SVC_CLOSE;
+ if (klp_cache_check(sn->rsi_cache, &rsip->h, &rqstp->rq_chandle) < 0)
+ /* No upcall result: */
+ return SVC_CLOSE;
+
+ ret = SVC_CLOSE;
+ /* Got an answer to the upcall; use it: */
+ if (klp_gss_write_init_verf(sn->rsc_cache, rqstp,
+ &rsip->out_handle, &rsip->major_status))
+ goto out;
+ if (klp_gss_write_resv(resv, PAGE_SIZE,
+ &rsip->out_handle, &rsip->out_token,
+ rsip->major_status, rsip->minor_status))
+ goto out;
+
+ ret = SVC_COMPLETE;
+out:
+ cache_put(&rsip->h, sn->rsi_cache);
+ return ret;
+}
+
+/* patched, only caller, svcauth_gss_accept(), also patched. */
+static int klp_svcauth_gss_proxy_init(struct svc_rqst *rqstp,
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +2 lines
+ */
+ struct rpc_gss_wire_cred *gc, __be32 *authp,
+ struct net *net)
+{
+ struct kvec *resv = &rqstp->rq_res.head[0];
+ struct xdr_netobj cli_handle;
+ struct gssp_upcall_data ud;
+ uint64_t handle;
+ int status;
+ int ret;
+ /*
+ * Fix CVE-2018-16884
+ * -1 line
+ */
+ struct sunrpc_net *sn = net_generic(net, (*klp_sunrpc_net_id));
+
+ memset(&ud, 0, sizeof(ud));
+ ret = klp_gss_read_proxy_verf(rqstp, gc, authp,
+ &ud.in_handle, &ud.in_token);
+ if (ret)
+ return ret;
+
+ ret = SVC_CLOSE;
+
+ /* Perform synchronous upcall to gss-proxy */
+ status = klp_gssp_accept_sec_context_upcall(net, &ud);
+ if (status)
+ goto out;
+
+ dprintk("RPC: svcauth_gss: gss major status = %d "
+ "minor status = %d\n",
+ ud.major_status, ud.minor_status);
+
+ switch (ud.major_status) {
+ case GSS_S_CONTINUE_NEEDED:
+ cli_handle = ud.out_handle;
+ break;
+ case GSS_S_COMPLETE:
+ status = klp_gss_proxy_save_rsc(sn->rsc_cache, &ud, &handle);
+ if (status)
+ goto out;
+ cli_handle.data = (u8 *)&handle;
+ cli_handle.len = sizeof(handle);
+ break;
+ default:
+ ret = SVC_CLOSE;
+ goto out;
+ }
+
+ /* Got an answer to the upcall; use it: */
+ if (klp_gss_write_init_verf(sn->rsc_cache, rqstp,
+ &cli_handle, &ud.major_status))
+ goto out;
+ if (klp_gss_write_resv(resv, PAGE_SIZE,
+ &cli_handle, &ud.out_token,
+ ud.major_status, ud.minor_status))
+ goto out;
+
+ ret = SVC_COMPLETE;
+out:
+ klp_gssp_free_upcall_data(&ud);
+ return ret;
+}
+
+/* patched */
+int klp_svcauth_gss_accept(struct svc_rqst *rqstp, __be32 *authp)
+{
+ struct kvec *argv = &rqstp->rq_arg.head[0];
+ struct kvec *resv = &rqstp->rq_res.head[0];
+ u32 crlen;
+ struct gss_svc_data *svcdata = rqstp->rq_auth_data;
+ struct rpc_gss_wire_cred *gc;
+ struct rsc *rsci = NULL;
+ __be32 *rpcstart;
+ __be32 *reject_stat = resv->iov_base + resv->iov_len;
+ int ret;
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +2 lines
+ */
+ struct net *net = klp_svc_net(rqstp);
+ struct sunrpc_net *sn = net_generic(net, (*klp_sunrpc_net_id));
+
+ dprintk("RPC: svcauth_gss: argv->iov_len = %zd\n",
+ argv->iov_len);
+
+ *authp = rpc_autherr_badcred;
+ if (!svcdata)
+ svcdata = kmalloc(sizeof(*svcdata), GFP_KERNEL);
+ if (!svcdata)
+ goto auth_err;
+ rqstp->rq_auth_data = svcdata;
+ svcdata->verf_start = NULL;
+ svcdata->rsci = NULL;
+ gc = &svcdata->clcred;
+
+ /* start of rpc packet is 7 u32's back from here:
+ * xid direction rpcversion prog vers proc flavour
+ */
+ rpcstart = argv->iov_base;
+ rpcstart -= 7;
+
+ /* credential is:
+ * version(==1), proc(0,1,2,3), seq, service (1,2,3), handle
+ * at least 5 u32s, and is preceded by length, so that makes 6.
+ */
+
+ if (argv->iov_len < 5 * 4)
+ goto auth_err;
+ crlen = svc_getnl(argv);
+ if (svc_getnl(argv) != RPC_GSS_VERSION)
+ goto auth_err;
+ gc->gc_proc = svc_getnl(argv);
+ gc->gc_seq = svc_getnl(argv);
+ gc->gc_svc = svc_getnl(argv);
+ if (klp_svc_safe_getnetobj(argv, &gc->gc_ctx))
+ goto auth_err;
+ if (crlen != klp_round_up_to_quad(gc->gc_ctx.len) + 5 * 4)
+ goto auth_err;
+
+ if ((gc->gc_proc != RPC_GSS_PROC_DATA) && (rqstp->rq_proc != 0))
+ goto auth_err;
+
+ *authp = rpc_autherr_badverf;
+ switch (gc->gc_proc) {
+ case RPC_GSS_PROC_INIT:
+ case RPC_GSS_PROC_CONTINUE_INIT:
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ if (klp_use_gss_proxy(net))
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ return klp_svcauth_gss_proxy_init(rqstp, gc, authp, net);
+ else
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ return klp_svcauth_gss_legacy_init(rqstp, gc, authp, net);
+ case RPC_GSS_PROC_DATA:
+ case RPC_GSS_PROC_DESTROY:
+ /* Look up the context, and check the verifier: */
+ *authp = rpcsec_gsserr_credproblem;
+ rsci = klp_gss_svc_searchbyctx(sn->rsc_cache, &gc->gc_ctx);
+ if (!rsci)
+ goto auth_err;
+ switch (klp_gss_verify_header(rqstp, rsci, rpcstart, gc, authp)) {
+ case SVC_OK:
+ break;
+ case SVC_DENIED:
+ goto auth_err;
+ case SVC_DROP:
+ goto drop;
+ }
+ break;
+ default:
+ *authp = rpc_autherr_rejectedcred;
+ goto auth_err;
+ }
+
+ /* now act upon the command: */
+ switch (gc->gc_proc) {
+ case RPC_GSS_PROC_DESTROY:
+ if (klp_gss_write_verf(rqstp, rsci->mechctx, gc->gc_seq))
+ goto auth_err;
+ /* Delete the entry from the cache_list and call cache_put */
+ klp_sunrpc_cache_unhash(sn->rsc_cache, &rsci->h);
+ if (resv->iov_len + 4 > PAGE_SIZE)
+ goto drop;
+ svc_putnl(resv, RPC_SUCCESS);
+ goto complete;
+ case RPC_GSS_PROC_DATA:
+ *authp = rpcsec_gsserr_ctxproblem;
+ svcdata->verf_start = resv->iov_base + resv->iov_len;
+ if (klp_gss_write_verf(rqstp, rsci->mechctx, gc->gc_seq))
+ goto auth_err;
+ rqstp->rq_cred = rsci->cred;
+ get_group_info(rsci->cred.cr_group_info);
+ *authp = rpc_autherr_badcred;
+ switch (gc->gc_svc) {
+ case RPC_GSS_SVC_NONE:
+ break;
+ case RPC_GSS_SVC_INTEGRITY:
+ /* placeholders for length and seq. number: */
+ svc_putnl(resv, 0);
+ svc_putnl(resv, 0);
+ if (klp_unwrap_integ_data(rqstp, &rqstp->rq_arg,
+ gc->gc_seq, rsci->mechctx))
+ goto garbage_args;
+ rqstp->rq_auth_slack = RPC_MAX_AUTH_SIZE;
+ break;
+ case RPC_GSS_SVC_PRIVACY:
+ /* placeholders for length and seq. number: */
+ svc_putnl(resv, 0);
+ svc_putnl(resv, 0);
+ if (klp_unwrap_priv_data(rqstp, &rqstp->rq_arg,
+ gc->gc_seq, rsci->mechctx))
+ goto garbage_args;
+ rqstp->rq_auth_slack = RPC_MAX_AUTH_SIZE * 2;
+ break;
+ default:
+ goto auth_err;
+ }
+ svcdata->rsci = rsci;
+ cache_get(&rsci->h);
+ rqstp->rq_cred.cr_flavor = klp_gss_svc_to_pseudoflavor(
+ rsci->mechctx->mech_type,
+ GSS_C_QOP_DEFAULT,
+ gc->gc_svc);
+ ret = SVC_OK;
+ goto out;
+ }
+garbage_args:
+ ret = SVC_GARBAGE;
+ goto out;
+auth_err:
+ /* Restore write pointer to its original value: */
+ xdr_ressize_check(rqstp, reject_stat);
+ ret = SVC_DENIED;
+ goto out;
+complete:
+ ret = SVC_COMPLETE;
+ goto out;
+drop:
+ ret = SVC_CLOSE;
+out:
+ if (rsci)
+ cache_put(&rsci->h, sn->rsc_cache);
+ return ret;
+}
+
+/* patched */
+int klp_svcauth_gss_release(struct svc_rqst *rqstp)
+{
+ struct gss_svc_data *gsd = (struct gss_svc_data *)rqstp->rq_auth_data;
+ struct rpc_gss_wire_cred *gc = &gsd->clcred;
+ struct xdr_buf *resbuf = &rqstp->rq_res;
+ int stat = -EINVAL;
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ struct sunrpc_net *sn = net_generic(klp_svc_net(rqstp), (*klp_sunrpc_net_id));
+
+ if (gc->gc_proc != RPC_GSS_PROC_DATA)
+ goto out;
+ /* Release can be called twice, but we only wrap once. */
+ if (gsd->verf_start == NULL)
+ goto out;
+ /* normally not set till svc_send, but we need it here: */
+ /* XXX: what for? Do we mess it up the moment we call svc_putu32
+ * or whatever? */
+ resbuf->len = klp_total_buf_len(resbuf);
+ switch (gc->gc_svc) {
+ case RPC_GSS_SVC_NONE:
+ break;
+ case RPC_GSS_SVC_INTEGRITY:
+ stat = klp_svcauth_gss_wrap_resp_integ(rqstp);
+ if (stat)
+ goto out_err;
+ break;
+ case RPC_GSS_SVC_PRIVACY:
+ stat = klp_svcauth_gss_wrap_resp_priv(rqstp);
+ if (stat)
+ goto out_err;
+ break;
+ /*
+ * For any other gc_svc value, svcauth_gss_accept() already set
+ * the auth_error appropriately; just fall through:
+ */
+ }
+
+out:
+ stat = 0;
+out_err:
+ if (rqstp->rq_client)
+ klp_auth_domain_put(rqstp->rq_client);
+ rqstp->rq_client = NULL;
+ if (rqstp->rq_gssclient)
+ klp_auth_domain_put(rqstp->rq_gssclient);
+ rqstp->rq_gssclient = NULL;
+ if (rqstp->rq_cred.cr_group_info)
+ put_group_info(rqstp->rq_cred.cr_group_info);
+ rqstp->rq_cred.cr_group_info = NULL;
+ if (gsd->rsci)
+ cache_put(&gsd->rsci->h, sn->rsc_cache);
+ gsd->rsci = NULL;
+
+ return stat;
+}
+
+
+
+static int livepatch_bsc1119947_auth_rpcgss_module_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct module *mod = data;
+ int ret;
+
+ if (action != MODULE_STATE_COMING || strcmp(mod->name, KLP_PATCHED_MODULE))
+ return 0;
+
+ ret = __klp_resolve_kallsyms_relocs(klp_funcs, ARRAY_SIZE(klp_funcs));
+ WARN(ret, "livepatch: delayed kallsyms lookup failed. System is broken and can crash.\n");
+
+ return ret;
+}
+
+static struct notifier_block livepatch_bsc1119947_auth_rpcgss_module_nb = {
+ .notifier_call = livepatch_bsc1119947_auth_rpcgss_module_notify,
+ .priority = INT_MIN+1,
+};
+
+int livepatch_bsc1119947_auth_rpcgss_init(void)
+{
+ int ret;
+
+ mutex_lock(&module_mutex);
+ if (find_module(KLP_PATCHED_MODULE)) {
+ ret = __klp_resolve_kallsyms_relocs(klp_funcs,
+ ARRAY_SIZE(klp_funcs));
+ if (ret)
+ goto out;
+ }
+
+ ret = register_module_notifier(&livepatch_bsc1119947_auth_rpcgss_module_nb);
+out:
+ mutex_unlock(&module_mutex);
+ return ret;
+}
+
+void livepatch_bsc1119947_auth_rpcgss_cleanup(void)
+{
+ unregister_module_notifier(&livepatch_bsc1119947_auth_rpcgss_module_nb);
+}
diff --git a/bsc1119947/livepatch_bsc1119947_auth_rpcgss.h b/bsc1119947/livepatch_bsc1119947_auth_rpcgss.h
new file mode 100644
index 0000000..ddf0182
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947_auth_rpcgss.h
@@ -0,0 +1,13 @@
+#ifndef _LIVEPATCH_BSC1119947_AUTH_RPCGSS_H
+#define _LIVEPATCH_BSC1119947_AUTH_RPCGSS_H
+
+int livepatch_bsc1119947_auth_rpcgss_init(void);
+void livepatch_bsc1119947_auth_rpcgss_cleanup(void);
+
+
+struct svc_rqst;
+
+int klp_svcauth_gss_accept(struct svc_rqst *rqstp, __be32 *authp);
+int klp_svcauth_gss_release(struct svc_rqst *rqstp);
+
+#endif /* _LIVEPATCH_BSC1119947_AUTH_RPCGSS_H */
diff --git a/bsc1119947/livepatch_bsc1119947_nfsv4.c b/bsc1119947/livepatch_bsc1119947_nfsv4.c
new file mode 100644
index 0000000..0a6b257
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947_nfsv4.c
@@ -0,0 +1,617 @@
+/*
+ * livepatch_bsc1119947_nfsv4
+ *
+ * Fix for CVE-2018-16884, bsc#1119947 -- nfsv4.ko part
+ *
+ * Copyright (c) 2019 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/nfs4.h>
+#include <linux/nfs_fs.h>
+#include "livepatch_bsc1119947.h"
+#include "kallsyms_relocs.h"
+
+#if !IS_MODULE(CONFIG_NFS_V4)
+#error "Live patch supports only CONFIG_NFS_V4=m"
+#endif
+
+#if !IS_ENABLED(CONFIG_NFS_V4_1)
+#error "Live patch supports only CONFIG_NFS_V4_1=y"
+#endif
+
+#if IS_ENABLED(CONFIG_NFS_V4_2)
+#error "Live patch supports only CONFIG_NFS_V4_2=n"
+#endif
+
+#if !IS_MODULE(CONFIG_NFS_FS)
+#error "Live patch supports only CONFIG_NFS_FS=m"
+#endif
+
+
+#define KLP_PATCHED_MODULE "nfsv4"
+
+
+struct cb_process_state;
+
+/* from fs/nfs/callback_xdr.c */
+typedef __be32 (*callback_process_op_t)(void *, void *,
+ struct cb_process_state *);
+typedef __be32 (*callback_decode_arg_t)(struct svc_rqst *, struct xdr_stream *, void *);
+typedef __be32 (*callback_encode_res_t)(struct svc_rqst *, struct xdr_stream *, void *);
+
+struct callback_op {
+ callback_process_op_t process_op;
+ callback_decode_arg_t decode_args;
+ callback_encode_res_t encode_res;
+ long res_maxsize;
+};
+
+
+
+static __be32 * (*klp_xdr_reserve_space)(struct xdr_stream *xdr, size_t nbytes);
+static __be32 *(*klp_xdr_encode_opaque)(__be32 *p, const void *ptr,
+ unsigned int nbytes);
+static __be32 * (*klp_xdr_inline_decode)(struct xdr_stream *xdr, size_t nbytes);
+static void (*klp_xdr_init_decode)(struct xdr_stream *xdr, struct xdr_buf *buf,
+ __be32 *p);
+static void (*klp_xdr_init_encode)(struct xdr_stream *xdr, struct xdr_buf *buf,
+ __be32 *p);
+static void (*klp_nfs_put_client)(struct nfs_client *clp);
+static struct callback_op (*klp_callback_ops)[];
+static void (*klp_nfs4_free_slot)(struct nfs4_slot_table *tbl,
+ struct nfs4_slot *slot);
+static struct nfs_client * (*klp_nfs4_find_client_ident)(struct net *net,
+ int cb_ident);
+static int (*klp_check_gss_callback_principal)(struct nfs_client *clp,
+ struct svc_rqst *rqstp);
+
+static struct klp_kallsyms_reloc klp_funcs[] = {
+ { "xdr_reserve_space", (void *)&klp_xdr_reserve_space, "sunrpc" },
+ { "xdr_encode_opaque", (void *)&klp_xdr_encode_opaque, "sunrpc" },
+ { "xdr_inline_decode", (void *)&klp_xdr_inline_decode, "sunrpc" },
+ { "xdr_init_decode", (void *)&klp_xdr_init_decode, "sunrpc" },
+ { "xdr_init_encode", (void *)&klp_xdr_init_encode, "sunrpc" },
+ { "nfs_put_client", (void *)&klp_nfs_put_client, "nfs" },
+ { "callback_ops", (void *)&klp_callback_ops, "nfsv4" },
+ { "nfs4_free_slot", (void *)&klp_nfs4_free_slot, "nfsv4" },
+ { "nfs4_find_client_ident", (void *)&klp_nfs4_find_client_ident,
+ "nfsv4" },
+ { "check_gss_callback_principal",
+ (void *)&klp_check_gss_callback_principal, "nfsv4" },
+};
+
+
+
+/* from fs/nfs/nfs4session.h */
+#define KLP_NFS4_MAX_SLOT_TABLE (1024U)
+
+#define KLP_SLOT_TABLE_SZ DIV_ROUND_UP(KLP_NFS4_MAX_SLOT_TABLE, 8*sizeof(long))
+
+struct nfs4_slot_table {
+ struct nfs4_session *session; /* Parent session */
+ struct nfs4_slot *slots; /* seqid per slot */
+ unsigned long used_slots[KLP_SLOT_TABLE_SZ]; /* used/unused bitmap */
+ spinlock_t slot_tbl_lock;
+ struct rpc_wait_queue slot_tbl_waitq; /* allocators may wait here */
+ wait_queue_head_t slot_waitq; /* Completion wait on slot */
+ u32 max_slots; /* # slots in table */
+ u32 max_slotid; /* Max allowed slotid value */
+ u32 highest_used_slotid; /* sent to server on each SEQ.
+ * op for dynamic resizing */
+ u32 target_highest_slotid; /* Server max_slot target */
+ u32 server_highest_slotid; /* Server highest slotid */
+ s32 d_target_highest_slotid; /* Derivative */
+ s32 d2_target_highest_slotid; /* 2nd derivative */
+ unsigned long generation; /* Generation counter for
+ target_highest_slotid */
+ struct completion complete;
+ unsigned long slot_tbl_state;
+};
+
+struct nfs4_session {
+ struct nfs4_sessionid sess_id;
+ u32 flags;
+ unsigned long session_state;
+ u32 hash_alg;
+ u32 ssv_len;
+
+ /* The fore and back channel */
+ struct nfs4_channel_attrs fc_attrs;
+ struct nfs4_slot_table fc_slot_table;
+ struct nfs4_channel_attrs bc_attrs;
+ struct nfs4_slot_table bc_slot_table;
+ struct nfs_client *clp;
+};
+
+
+/* from fs/nfs/callback.h */
+enum nfs4_callback_opnum {
+ OP_CB_GETATTR = 3,
+ OP_CB_RECALL = 4,
+/* Callback operations new to NFSv4.1 */
+ OP_CB_LAYOUTRECALL = 5,
+ OP_CB_NOTIFY = 6,
+ OP_CB_PUSH_DELEG = 7,
+ OP_CB_RECALL_ANY = 8,
+ OP_CB_RECALLABLE_OBJ_AVAIL = 9,
+ OP_CB_RECALL_SLOT = 10,
+ OP_CB_SEQUENCE = 11,
+ OP_CB_WANTS_CANCELLED = 12,
+ OP_CB_NOTIFY_LOCK = 13,
+ OP_CB_NOTIFY_DEVICEID = 14,
+/* Callback operations new to NFSv4.2 */
+ OP_CB_OFFLOAD = 15,
+ OP_CB_ILLEGAL = 10044,
+};
+
+struct cb_process_state {
+ __be32 drc_status;
+ struct nfs_client *clp;
+ struct nfs4_slot *slot;
+ u32 minorversion;
+ struct net *net;
+};
+
+struct cb_compound_hdr_arg {
+ unsigned int taglen;
+ const char *tag;
+ unsigned int minorversion;
+ unsigned int cb_ident; /* v4.0 callback identifier */
+ unsigned nops;
+};
+
+struct cb_compound_hdr_res {
+ __be32 *status;
+ unsigned int taglen;
+ const char *tag;
+ __be32 *nops;
+};
+
+
+/* from fs/nfs/nfs4_fs.h */
+#if defined(CONFIG_NFS_V4_2)
+#define KLP_NFS4_MAX_MINOR_VERSION 2
+#elif defined(CONFIG_NFS_V4_1)
+#define KLP_NFS4_MAX_MINOR_VERSION 1
+#else
+#define KLP_NFS4_MAX_MINOR_VERSION 0
+#endif
+
+
+/* from include/linux/net/sunrpc/xdr.h */
+/* resolve reference to xdr_reserve_space() + xdr_encode_opaque() exports */
+static inline ssize_t
+klp_xdr_stream_encode_opaque(struct xdr_stream *xdr, const void *ptr,
+ size_t len)
+{
+ size_t count = sizeof(__u32) + xdr_align_size(len);
+ __be32 *p = klp_xdr_reserve_space(xdr, count);
+
+ if (unlikely(!p))
+ return -EMSGSIZE;
+ klp_xdr_encode_opaque(p, ptr, len);
+ return count;
+}
+
+/* resolve reference to xdr_inline_decode() export */
+static inline ssize_t
+klp_xdr_stream_decode_u32(struct xdr_stream *xdr, __u32 *ptr)
+{
+ const size_t count = sizeof(*ptr);
+ __be32 *p = klp_xdr_inline_decode(xdr, count);
+
+ if (unlikely(!p))
+ return -EBADMSG;
+ *ptr = be32_to_cpup(p);
+ return 0;
+}
+
+/* resolve reference to xdr_inline_decode() export */
+static inline ssize_t
+klp_xdr_stream_decode_opaque_inline(struct xdr_stream *xdr, void **ptr,
+ size_t maxlen)
+{
+ __be32 *p;
+ __u32 len;
+
+ *ptr = NULL;
+ if (unlikely(klp_xdr_stream_decode_u32(xdr, &len) < 0))
+ return -EBADMSG;
+ if (len != 0) {
+ p = klp_xdr_inline_decode(xdr, len);
+ if (unlikely(!p))
+ return -EBADMSG;
+ if (unlikely(len > maxlen))
+ return -EMSGSIZE;
+ *ptr = p;
+ }
+ return len;
+}
+
+
+/* from fs/nfs/callback_xdr.c */
+#define KLP_CB_OP_TAGLEN_MAXSZ (512)
+
+#define KLP_NFS4ERR_RESOURCE_HDR 11050
+
+/* inlined */
+static __be32 *klp_read_buf(struct xdr_stream *xdr, size_t nbytes)
+{
+ __be32 *p;
+
+ p = klp_xdr_inline_decode(xdr, nbytes);
+ if (unlikely(p == NULL))
+ printk(KERN_WARNING "NFS: NFSv4 callback reply buffer overflowed!\n");
+ return p;
+}
+
+/* inlined */
+static __be32 klp_decode_string(struct xdr_stream *xdr, unsigned int *len,
+ const char **str, size_t maxlen)
+{
+ ssize_t err;
+
+ err = klp_xdr_stream_decode_opaque_inline(xdr, (void **)str, maxlen);
+ if (err < 0)
+ return cpu_to_be32(NFS4ERR_RESOURCE);
+ *len = err;
+ return 0;
+}
+
+/* inlined */
+static __be32 klp_decode_compound_hdr_arg(struct xdr_stream *xdr,
+ struct cb_compound_hdr_arg *hdr)
+{
+ __be32 *p;
+ __be32 status;
+
+ status = klp_decode_string(xdr, &hdr->taglen, &hdr->tag, KLP_CB_OP_TAGLEN_MAXSZ);
+ if (unlikely(status != 0))
+ return status;
+ p = klp_read_buf(xdr, 12);
+ if (unlikely(p == NULL))
+ return htonl(NFS4ERR_RESOURCE);
+ hdr->minorversion = ntohl(*p++);
+ /* Check for minor version support */
+ if (hdr->minorversion <= KLP_NFS4_MAX_MINOR_VERSION) {
+ hdr->cb_ident = ntohl(*p++); /* ignored by v4.1 and v4.2 */
+ } else {
+ pr_warn_ratelimited("NFS: %s: NFSv4 server callback with "
+ "illegal minor version %u!\n",
+ __func__, hdr->minorversion);
+ return htonl(NFS4ERR_MINOR_VERS_MISMATCH);
+ }
+ hdr->nops = ntohl(*p);
+ return 0;
+}
+
+/* inlined */
+static __be32 klp_decode_op_hdr(struct xdr_stream *xdr, unsigned int *op)
+{
+ __be32 *p;
+ p = klp_read_buf(xdr, 4);
+ if (unlikely(p == NULL))
+ return htonl(KLP_NFS4ERR_RESOURCE_HDR);
+ *op = ntohl(*p);
+ return 0;
+}
+
+/* inlined; careful: there are two, incompatible implementations in nfsv4.ko */
+static __be32 klp_encode_string(struct xdr_stream *xdr, unsigned int len,
+ const char *str)
+{
+ if (unlikely(klp_xdr_stream_encode_opaque(xdr, str, len) < 0))
+ return cpu_to_be32(NFS4ERR_RESOURCE);
+ return 0;
+}
+
+/* inlined */
+static __be32 klp_encode_compound_hdr_res(struct xdr_stream *xdr,
+ struct cb_compound_hdr_res *hdr)
+{
+ __be32 status;
+
+ hdr->status = klp_xdr_reserve_space(xdr, 4);
+ if (unlikely(hdr->status == NULL))
+ return htonl(NFS4ERR_RESOURCE);
+ status = klp_encode_string(xdr, hdr->taglen, hdr->tag);
+ if (unlikely(status != 0))
+ return status;
+ hdr->nops = klp_xdr_reserve_space(xdr, 4);
+ if (unlikely(hdr->nops == NULL))
+ return htonl(NFS4ERR_RESOURCE);
+ return 0;
+}
+
+/* inlined */
+static __be32 klp_encode_op_hdr(struct xdr_stream *xdr, uint32_t op, __be32 res)
+{
+ __be32 *p;
+
+ p = klp_xdr_reserve_space(xdr, 8);
+ if (unlikely(p == NULL))
+ return htonl(KLP_NFS4ERR_RESOURCE_HDR);
+ *p++ = htonl(op);
+ *p = res;
+ return 0;
+}
+
+/* inlined */
+static __be32
+klp_preprocess_nfs41_op(int nop, unsigned int op_nr, struct callback_op **op)
+{
+ if (op_nr == OP_CB_SEQUENCE) {
+ if (nop != 0)
+ return htonl(NFS4ERR_SEQUENCE_POS);
+ } else {
+ if (nop == 0)
+ return htonl(NFS4ERR_OP_NOT_IN_SESSION);
+ }
+
+ switch (op_nr) {
+ case OP_CB_GETATTR:
+ case OP_CB_RECALL:
+ case OP_CB_SEQUENCE:
+ case OP_CB_RECALL_ANY:
+ case OP_CB_RECALL_SLOT:
+ case OP_CB_LAYOUTRECALL:
+ case OP_CB_NOTIFY_DEVICEID:
+ case OP_CB_NOTIFY_LOCK:
+ *op = &(*klp_callback_ops)[op_nr];
+ break;
+
+ case OP_CB_NOTIFY:
+ case OP_CB_PUSH_DELEG:
+ case OP_CB_RECALLABLE_OBJ_AVAIL:
+ case OP_CB_WANTS_CANCELLED:
+ return htonl(NFS4ERR_NOTSUPP);
+
+ default:
+ return htonl(NFS4ERR_OP_ILLEGAL);
+ }
+
+ return htonl(NFS_OK);
+}
+
+/* inlined */
+static void klp_nfs4_callback_free_slot(struct nfs4_session *session,
+ struct nfs4_slot *slot)
+{
+ struct nfs4_slot_table *tbl = &session->bc_slot_table;
+
+ spin_lock(&tbl->slot_tbl_lock);
+ /*
+ * Let the state manager know callback processing done.
+ * A single slot, so highest used slotid is either 0 or -1
+ */
+ klp_nfs4_free_slot(tbl, slot);
+ spin_unlock(&tbl->slot_tbl_lock);
+}
+
+/* inlined */
+static void klp_nfs4_cb_free_slot(struct cb_process_state *cps)
+{
+ if (cps->slot) {
+ klp_nfs4_callback_free_slot(cps->clp->cl_session, cps->slot);
+ cps->slot = NULL;
+ }
+}
+
+static __be32
+klp_preprocess_nfs42_op(int nop, unsigned int op_nr, struct callback_op **op)
+{
+ return htonl(NFS4ERR_MINOR_VERS_MISMATCH);
+}
+
+/* inlined */
+static __be32
+klp_preprocess_nfs4_op(unsigned int op_nr, struct callback_op **op)
+{
+ switch (op_nr) {
+ case OP_CB_GETATTR:
+ case OP_CB_RECALL:
+ *op = &(*klp_callback_ops)[op_nr];
+ break;
+ default:
+ return htonl(NFS4ERR_OP_ILLEGAL);
+ }
+
+ return htonl(NFS_OK);
+}
+
+/* inlined */
+static __be32 klp_process_op(int nop, struct svc_rqst *rqstp,
+ struct xdr_stream *xdr_in, void *argp,
+ struct xdr_stream *xdr_out, void *resp,
+ struct cb_process_state *cps)
+{
+ struct callback_op *op = &(*klp_callback_ops)[0];
+ unsigned int op_nr;
+ __be32 status;
+ long maxlen;
+ __be32 res;
+
+ status = klp_decode_op_hdr(xdr_in, &op_nr);
+ if (unlikely(status))
+ return status;
+
+ switch (cps->minorversion) {
+ case 0:
+ status = klp_preprocess_nfs4_op(op_nr, &op);
+ break;
+ case 1:
+ status = klp_preprocess_nfs41_op(nop, op_nr, &op);
+ break;
+ case 2:
+ status = klp_preprocess_nfs42_op(nop, op_nr, &op);
+ break;
+ default:
+ status = htonl(NFS4ERR_MINOR_VERS_MISMATCH);
+ }
+
+ if (status == htonl(NFS4ERR_OP_ILLEGAL))
+ op_nr = OP_CB_ILLEGAL;
+ if (status)
+ goto encode_hdr;
+
+ if (cps->drc_status) {
+ status = cps->drc_status;
+ goto encode_hdr;
+ }
+
+ maxlen = xdr_out->end - xdr_out->p;
+ if (maxlen > 0 && maxlen < PAGE_SIZE) {
+ status = op->decode_args(rqstp, xdr_in, argp);
+ if (likely(status == 0))
+ status = op->process_op(argp, resp, cps);
+ } else
+ status = htonl(NFS4ERR_RESOURCE);
+
+encode_hdr:
+ res = klp_encode_op_hdr(xdr_out, op_nr, status);
+ if (unlikely(res))
+ return res;
+ if (op->encode_res != NULL && status == 0)
+ status = op->encode_res(rqstp, xdr_out, resp);
+ return status;
+}
+
+
+
+/* patched */
+__be32 klp_nfs4_callback_compound(struct svc_rqst *rqstp, void *argp,
+ void *resp)
+{
+ struct cb_compound_hdr_arg hdr_arg = { 0 };
+ struct cb_compound_hdr_res hdr_res = { NULL };
+ struct xdr_stream xdr_in, xdr_out;
+ __be32 *p, status;
+ /*
+ * Fix CVE-2018-16884
+ * +1 line
+ */
+ struct net *net = klp_svc_net(rqstp);
+ struct cb_process_state cps = {
+ .drc_status = 0,
+ .clp = NULL,
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ .net = net,
+ };
+ unsigned int nops = 0;
+
+ klp_xdr_init_decode(&xdr_in, &rqstp->rq_arg, rqstp->rq_arg.head[0].iov_base);
+
+ p = (__be32*)((char *)rqstp->rq_res.head[0].iov_base + rqstp->rq_res.head[0].iov_len);
+ klp_xdr_init_encode(&xdr_out, &rqstp->rq_res, p);
+
+ status = klp_decode_compound_hdr_arg(&xdr_in, &hdr_arg);
+ if (status == htonl(NFS4ERR_RESOURCE))
+ return rpc_garbage_args;
+
+ if (hdr_arg.minorversion == 0) {
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ cps.clp = klp_nfs4_find_client_ident(net, hdr_arg.cb_ident);
+ if (!cps.clp || !klp_check_gss_callback_principal(cps.clp, rqstp))
+ goto out_invalidcred;
+ }
+
+ cps.minorversion = hdr_arg.minorversion;
+ hdr_res.taglen = hdr_arg.taglen;
+ hdr_res.tag = hdr_arg.tag;
+ if (klp_encode_compound_hdr_res(&xdr_out, &hdr_res) != 0)
+ return rpc_system_err;
+
+ while (status == 0 && nops != hdr_arg.nops) {
+ status = klp_process_op(nops, rqstp, &xdr_in,
+ argp, &xdr_out, resp, &cps);
+ nops++;
+ }
+
+ /* Buffer overflow in decode_ops_hdr or encode_ops_hdr. Return
+ * resource error in cb_compound status without returning op */
+ if (unlikely(status == htonl(KLP_NFS4ERR_RESOURCE_HDR))) {
+ status = htonl(NFS4ERR_RESOURCE);
+ nops--;
+ }
+
+ *hdr_res.status = status;
+ *hdr_res.nops = htonl(nops);
+ klp_nfs4_cb_free_slot(&cps);
+ klp_nfs_put_client(cps.clp);
+ return rpc_success;
+
+out_invalidcred:
+ pr_warn_ratelimited("NFS: NFSv4 callback contains invalid cred\n");
+ return rpc_autherr_badcred;
+}
+
+
+
+static int livepatch_bsc1119947_nfsv4_module_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct module *mod = data;
+ int ret;
+
+ if (action != MODULE_STATE_COMING || strcmp(mod->name, KLP_PATCHED_MODULE))
+ return 0;
+
+ ret = __klp_resolve_kallsyms_relocs(klp_funcs, ARRAY_SIZE(klp_funcs));
+ WARN(ret, "livepatch: delayed kallsyms lookup failed. System is broken and can crash.\n");
+
+ return ret;
+}
+
+static struct notifier_block livepatch_bsc1119947_nfsv4_module_nb = {
+ .notifier_call = livepatch_bsc1119947_nfsv4_module_notify,
+ .priority = INT_MIN+1,
+};
+
+int livepatch_bsc1119947_nfsv4_init(void)
+{
+ int ret;
+
+ mutex_lock(&module_mutex);
+ if (find_module(KLP_PATCHED_MODULE)) {
+ ret = __klp_resolve_kallsyms_relocs(klp_funcs,
+ ARRAY_SIZE(klp_funcs));
+ if (ret)
+ goto out;
+ }
+
+ ret = register_module_notifier(&livepatch_bsc1119947_nfsv4_module_nb);
+out:
+ mutex_unlock(&module_mutex);
+ return ret;
+}
+
+void livepatch_bsc1119947_nfsv4_cleanup(void)
+{
+ unregister_module_notifier(&livepatch_bsc1119947_nfsv4_module_nb);
+}
diff --git a/bsc1119947/livepatch_bsc1119947_nfsv4.h b/bsc1119947/livepatch_bsc1119947_nfsv4.h
new file mode 100644
index 0000000..961f8c9
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947_nfsv4.h
@@ -0,0 +1,13 @@
+#ifndef _LIVEPATCH_BSC1119947_NFSV4_H
+#define _LIVEPATCH_BSC1119947_NFSV4_H
+
+int livepatch_bsc1119947_nfsv4_init(void);
+void livepatch_bsc1119947_nfsv4_cleanup(void);
+
+
+struct svc_rqst;
+
+__be32 klp_nfs4_callback_compound(struct svc_rqst *rqstp, void *argp,
+ void *resp);
+
+#endif /* _LIVEPATCH_BSC1119947_NFSV4_H */
diff --git a/bsc1119947/livepatch_bsc1119947_sunrpc.c b/bsc1119947/livepatch_bsc1119947_sunrpc.c
new file mode 100644
index 0000000..cf2189a
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947_sunrpc.c
@@ -0,0 +1,498 @@
+/*
+ * livepatch_bsc1119947_sunrpc
+ *
+ * Fix for CVE-2018-16884, bsc#1119947 -- sunrpc.ko part
+ *
+ * Copyright (c) 2019 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/sunrpc/svc_xprt.h>
+#include <linux/sunrpc/xprt.h>
+#include <linux/sunrpc/stats.h>
+#include <linux/sunrpc/debug.h>
+#include "livepatch_bsc1119947.h"
+#include "kallsyms_relocs.h"
+
+#if !IS_MODULE(CONFIG_SUNRPC)
+#error "Live patch supports only CONFIG_SUNRPC=m"
+#endif
+
+#if !IS_ENABLED(CONFIG_SUNRPC_DEBUG)
+#error "Live patch supports only CONFIG_SUNRPC_DEBUG=y"
+#endif
+
+#if !IS_ENABLED(CONFIG_SUNRPC_BACKCHANNEL)
+#error "Live patch supports only CONFIG_SUNRPC_BACKCHANNEL=y"
+#endif
+
+
+#define KLP_PATCHED_MODULE "sunrpc"
+
+
+static unsigned int *klp_rpc_debug;
+
+static int (*klp_svc_authenticate)(struct svc_rqst *rqstp, __be32 *authp);
+static int (*klp_svc_authorise)(struct svc_rqst *rqstp);
+static void (__printf(2, 3) * klp_svc_printk)(struct svc_rqst *rqstp,
+ const char *fmt, ...);
+static void (*klp_xprt_free_bc_request)(struct rpc_rqst *req);
+static struct rpc_task *(*klp_rpc_run_bc_task)(struct rpc_rqst *req);
+static void (*klp_rpc_put_task)(struct rpc_task *task);
+
+static struct klp_kallsyms_reloc klp_funcs[] = {
+ { "rpc_debug", (void *)&klp_rpc_debug, "sunrpc" },
+ { "svc_authenticate", (void *)&klp_svc_authenticate, "sunrpc" },
+ { "svc_authorise", (void *)&klp_svc_authorise, "sunrpc" },
+ { "svc_printk", (void *)&klp_svc_printk, "sunrpc" },
+ { "xprt_free_bc_request", (void *)&klp_xprt_free_bc_request, "sunrpc" },
+ { "rpc_run_bc_task", (void *)&klp_rpc_run_bc_task, "sunrpc" },
+ { "rpc_put_task", (void *)&klp_rpc_put_task, "sunrpc" },
+};
+
+
+
+/* from include/linux/sunrpc/debug.h */
+/* resolve rpc_debug */
+#undef ifdebug
+# define ifdebug(fac) if (unlikely((*klp_rpc_debug) & RPCDBG_##fac))
+
+
+/* from net/sunrpc/svc.c */
+#define RPCDBG_FACILITY RPCDBG_SVCDSP
+
+
+
+/*
+ * Patched, behaviour is different only if called from also patched
+ * klp_bc_svc_process(): act as if rqstp->rq_xprt had been set to
+ * NULL.
+ */
+static int
+klp_svc_process_common(struct svc_rqst *rqstp, struct kvec *argv,
+ struct kvec *resv)
+{
+ struct svc_program *progp;
+ struct svc_version *versp = NULL; /* compiler food */
+ struct svc_procedure *procp = NULL;
+ struct svc_serv *serv = rqstp->rq_server;
+ kxdrproc_t xdr;
+ __be32 *statp;
+ u32 prog, vers, proc;
+ __be32 auth_stat, rpc_stat;
+ int auth_res;
+ __be32 *reply_statp;
+
+ rpc_stat = rpc_success;
+
+ if (argv->iov_len < 6*4)
+ goto err_short_len;
+
+ /* Will be turned off only in gss privacy case: */
+ set_bit(RQ_SPLICE_OK, &rqstp->rq_flags);
+ /* Will be turned off only when NFSv4 Sessions are used */
+ set_bit(RQ_USEDEFERRAL, &rqstp->rq_flags);
+ clear_bit(RQ_DROPME, &rqstp->rq_flags);
+
+ /* Setup reply header */
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +6 lines
+ */
+ if (rqstp->rq_prot == IPPROTO_TCP) {
+ struct kvec *resv = &rqstp->rq_res.head[0];
+
+ /* tcp needs a space for the record length... */
+ svc_putnl(resv, 0);
+ }
+
+ svc_putu32(resv, rqstp->rq_xid);
+
+ vers = svc_getnl(argv);
+
+ /* First words of reply: */
+ svc_putnl(resv, 1); /* REPLY */
+
+ if (vers != 2) /* RPC version number */
+ goto err_bad_rpc;
+
+ /* Save position in case we later decide to reject: */
+ reply_statp = resv->iov_base + resv->iov_len;
+
+ svc_putnl(resv, 0); /* ACCEPT */
+
+ rqstp->rq_prog = prog = svc_getnl(argv); /* program number */
+ rqstp->rq_vers = vers = svc_getnl(argv); /* version number */
+ rqstp->rq_proc = proc = svc_getnl(argv); /* procedure number */
+
+ for (progp = serv->sv_program; progp; progp = progp->pg_next)
+ if (prog == progp->pg_prog)
+ break;
+
+ /*
+ * Decode auth data, and add verifier to reply buffer.
+ * We do this before anything else in order to get a decent
+ * auth verifier.
+ */
+ auth_res = klp_svc_authenticate(rqstp, &auth_stat);
+ /* Also give the program a chance to reject this call: */
+ if (auth_res == SVC_OK && progp) {
+ auth_stat = rpc_autherr_badcred;
+ auth_res = progp->pg_authenticate(rqstp);
+ }
+ switch (auth_res) {
+ case SVC_OK:
+ break;
+ case SVC_GARBAGE:
+ goto err_garbage;
+ case SVC_SYSERR:
+ rpc_stat = rpc_system_err;
+ goto err_bad;
+ case SVC_DENIED:
+ goto err_bad_auth;
+ case SVC_CLOSE:
+ goto close;
+ case SVC_DROP:
+ goto dropit;
+ case SVC_COMPLETE:
+ goto sendit;
+ }
+
+ if (progp == NULL)
+ goto err_bad_prog;
+
+ if (vers >= progp->pg_nvers ||
+ !(versp = progp->pg_vers[vers]))
+ goto err_bad_vers;
+
+ /*
+ * Some protocol versions (namely NFSv4) require some form of
+ * congestion control. (See RFC 7530 section 3.1 paragraph 2)
+ * In other words, UDP is not allowed. We mark those when setting
+ * up the svc_xprt, and verify that here.
+ *
+ * The spec is not very clear about what error should be returned
+ * when someone tries to access a server that is listening on UDP
+ * for lower versions. RPC_PROG_MISMATCH seems to be the closest
+ * fit.
+ */
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ if (versp->vs_need_cong_ctrl && NULL &&
+ !test_bit(XPT_CONG_CTRL, &rqstp->rq_xprt->xpt_flags))
+ goto err_bad_vers;
+
+ procp = versp->vs_proc + proc;
+ if (proc >= versp->vs_nproc || !procp->pc_func)
+ goto err_bad_proc;
+ rqstp->rq_procinfo = procp;
+
+ /* Syntactic check complete */
+ serv->sv_stats->rpccnt++;
+
+ /* Build the reply header. */
+ statp = resv->iov_base +resv->iov_len;
+ svc_putnl(resv, RPC_SUCCESS);
+
+ /* Bump per-procedure stats counter */
+ procp->pc_count++;
+
+ /* Initialize storage for argp and resp */
+ memset(rqstp->rq_argp, 0, procp->pc_argsize);
+ memset(rqstp->rq_resp, 0, procp->pc_ressize);
+
+ /* un-reserve some of the out-queue now that we have a
+ * better idea of reply size
+ */
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ *
+ * Explanation: the upstream fix patches svc_reserve() into a
+ * nop for the case that vrqstp->rq_xptr == NULL, i.e. for
+ * vrqstp as setup by bc_svc_process(). The
+ * svc_reserve_auth() invocation below is the only place where
+ * svc_reserve() gets called from bc_svc_process(). So, rather
+ * than live patching svc_reserve(), just mask that call.
+ */
+ if (NULL && procp->pc_xdrressize)
+ svc_reserve_auth(rqstp, procp->pc_xdrressize<<2);
+
+ /* Call the function that processes the request. */
+ if (!versp->vs_dispatch) {
+ /* Decode arguments */
+ xdr = procp->pc_decode;
+ if (xdr && !xdr(rqstp, argv->iov_base, rqstp->rq_argp))
+ goto err_garbage;
+
+ *statp = procp->pc_func(rqstp, rqstp->rq_argp, rqstp->rq_resp);
+
+ /* Encode reply */
+ if (*statp == rpc_drop_reply ||
+ test_bit(RQ_DROPME, &rqstp->rq_flags)) {
+ if (procp->pc_release)
+ procp->pc_release(rqstp, NULL, rqstp->rq_resp);
+ goto dropit;
+ }
+ if (*statp == rpc_autherr_badcred) {
+ if (procp->pc_release)
+ procp->pc_release(rqstp, NULL, rqstp->rq_resp);
+ goto err_bad_auth;
+ }
+ if (*statp == rpc_success &&
+ (xdr = procp->pc_encode) &&
+ !xdr(rqstp, resv->iov_base+resv->iov_len, rqstp->rq_resp)) {
+ dprintk("svc: failed to encode reply\n");
+ /* serv->sv_stats->rpcsystemerr++; */
+ *statp = rpc_system_err;
+ }
+ } else {
+ dprintk("svc: calling dispatcher\n");
+ if (!versp->vs_dispatch(rqstp, statp)) {
+ /* Release reply info */
+ if (procp->pc_release)
+ procp->pc_release(rqstp, NULL, rqstp->rq_resp);
+ goto dropit;
+ }
+ }
+
+ /* Check RPC status result */
+ if (*statp != rpc_success)
+ resv->iov_len = ((void*)statp) - resv->iov_base + 4;
+
+ /* Release reply info */
+ if (procp->pc_release)
+ procp->pc_release(rqstp, NULL, rqstp->rq_resp);
+
+ if (procp->pc_encode == NULL)
+ goto dropit;
+
+ sendit:
+ if (klp_svc_authorise(rqstp))
+ goto close;
+ return 1; /* Caller can now send it */
+
+ dropit:
+ klp_svc_authorise(rqstp); /* doesn't hurt to call this twice */
+ dprintk("svc: svc_process dropit\n");
+ return 0;
+
+ close:
+ /*
+ * Fix CVE-2018-16884
+ * -1 line, +1 line
+ */
+ if (NULL && test_bit(XPT_TEMP, &rqstp->rq_xprt->xpt_flags))
+ svc_close_xprt(rqstp->rq_xprt);
+ dprintk("svc: svc_process close\n");
+ return 0;
+
+err_short_len:
+ klp_svc_printk(rqstp, "short len %zd, dropping request\n",
+ argv->iov_len);
+
+ goto close;
+
+err_bad_rpc:
+ serv->sv_stats->rpcbadfmt++;
+ svc_putnl(resv, 1); /* REJECT */
+ svc_putnl(resv, 0); /* RPC_MISMATCH */
+ svc_putnl(resv, 2); /* Only RPCv2 supported */
+ svc_putnl(resv, 2);
+ goto sendit;
+
+err_bad_auth:
+ dprintk("svc: authentication failed (%d)\n", ntohl(auth_stat));
+ serv->sv_stats->rpcbadauth++;
+ /* Restore write pointer to location of accept status: */
+ xdr_ressize_check(rqstp, reply_statp);
+ svc_putnl(resv, 1); /* REJECT */
+ svc_putnl(resv, 1); /* AUTH_ERROR */
+ svc_putnl(resv, ntohl(auth_stat)); /* status */
+ goto sendit;
+
+err_bad_prog:
+ dprintk("svc: unknown program %d\n", prog);
+ serv->sv_stats->rpcbadfmt++;
+ svc_putnl(resv, RPC_PROG_UNAVAIL);
+ goto sendit;
+
+err_bad_vers:
+ klp_svc_printk(rqstp, "unknown version (%d for prog %d, %s)\n",
+ vers, prog, progp->pg_name);
+
+ serv->sv_stats->rpcbadfmt++;
+ svc_putnl(resv, RPC_PROG_MISMATCH);
+ svc_putnl(resv, progp->pg_lovers);
+ svc_putnl(resv, progp->pg_hivers);
+ goto sendit;
+
+err_bad_proc:
+ klp_svc_printk(rqstp, "unknown procedure (%d)\n", proc);
+
+ serv->sv_stats->rpcbadfmt++;
+ svc_putnl(resv, RPC_PROC_UNAVAIL);
+ goto sendit;
+
+err_garbage:
+ klp_svc_printk(rqstp, "failed to decode args\n");
+
+ rpc_stat = rpc_garbage_args;
+err_bad:
+ serv->sv_stats->rpcbadfmt++;
+ svc_putnl(resv, ntohl(rpc_stat));
+ goto sendit;
+}
+
+/* patched */
+int klp_bc_svc_process(struct svc_serv *serv, struct rpc_rqst *req,
+ struct svc_rqst *rqstp)
+{
+ struct kvec *argv = &rqstp->rq_arg.head[0];
+ struct kvec *resv = &rqstp->rq_res.head[0];
+ struct rpc_task *task;
+ int proc_error;
+ int error;
+
+ dprintk("svc: %s(%p)\n", __func__, req);
+
+ /* Build the svc_rqst used by the common processing routine */
+ rqstp->rq_xprt = serv->sv_bc_xprt;
+ rqstp->rq_xid = req->rq_xid;
+ rqstp->rq_prot = req->rq_xprt->prot;
+ rqstp->rq_server = serv;
+ /*
+ * Fix CVE-2018-16884
+ * +1 line
+ *
+ * Note that the above assignment to rqstp->rq_xprt is left
+ * in place because unpatched third-party modules might call
+ * bc_svc_process() and access that field. Don't break those.
+ */
+ klp_shadow_rq_bc_net_set(rqstp, req->rq_xprt->xprt_net);
+
+ rqstp->rq_addrlen = sizeof(req->rq_xprt->addr);
+ memcpy(&rqstp->rq_addr, &req->rq_xprt->addr, rqstp->rq_addrlen);
+ memcpy(&rqstp->rq_arg, &req->rq_rcv_buf, sizeof(rqstp->rq_arg));
+ memcpy(&rqstp->rq_res, &req->rq_snd_buf, sizeof(rqstp->rq_res));
+
+ /* Adjust the argument buffer length */
+ rqstp->rq_arg.len = req->rq_private_buf.len;
+ if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0].iov_len) {
+ rqstp->rq_arg.head[0].iov_len = rqstp->rq_arg.len;
+ rqstp->rq_arg.page_len = 0;
+ } else if (rqstp->rq_arg.len <= rqstp->rq_arg.head[0].iov_len +
+ rqstp->rq_arg.page_len)
+ rqstp->rq_arg.page_len = rqstp->rq_arg.len -
+ rqstp->rq_arg.head[0].iov_len;
+ else
+ rqstp->rq_arg.len = rqstp->rq_arg.head[0].iov_len +
+ rqstp->rq_arg.page_len;
+
+ /* reset result send buffer "put" position */
+ resv->iov_len = 0;
+
+ /*
+ * Skip the next two words because they've already been
+ * processed in the transport
+ */
+ svc_getu32(argv); /* XID */
+ svc_getnl(argv); /* CALLDIR */
+
+ /* Parse and execute the bc call */
+ proc_error = klp_svc_process_common(rqstp, argv, resv);
+
+ /*
+ * Fix CVE-2018-16884
+ * +2 lines
+ */
+ klp_shadow_rq_bc_net_destroy(rqstp);
+
+ atomic_inc(&req->rq_xprt->bc_free_slots);
+ if (!proc_error) {
+ /* Processing error: drop the request */
+ klp_xprt_free_bc_request(req);
+ return 0;
+ }
+
+ /* Finally, send the reply synchronously */
+ memcpy(&req->rq_snd_buf, &rqstp->rq_res, sizeof(req->rq_snd_buf));
+ task = klp_rpc_run_bc_task(req);
+ if (IS_ERR(task)) {
+ error = PTR_ERR(task);
+ goto out;
+ }
+
+ WARN_ON_ONCE(atomic_read(&task->tk_count) != 1);
+ error = task->tk_status;
+ klp_rpc_put_task(task);
+
+out:
+ dprintk("svc: %s(), error=%d\n", __func__, error);
+ return error;
+}
+
+
+
+static int livepatch_bsc1119947_sunrpc_module_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct module *mod = data;
+ int ret;
+
+ if (action != MODULE_STATE_COMING || strcmp(mod->name, KLP_PATCHED_MODULE))
+ return 0;
+
+ ret = __klp_resolve_kallsyms_relocs(klp_funcs, ARRAY_SIZE(klp_funcs));
+ WARN(ret, "livepatch: delayed kallsyms lookup failed. System is broken and can crash.\n");
+
+ return ret;
+}
+
+static struct notifier_block livepatch_bsc1119947_sunrpc_module_nb = {
+ .notifier_call = livepatch_bsc1119947_sunrpc_module_notify,
+ .priority = INT_MIN+1,
+};
+
+int livepatch_bsc1119947_sunrpc_init(void)
+{
+ int ret;
+
+ mutex_lock(&module_mutex);
+ if (find_module(KLP_PATCHED_MODULE)) {
+ ret = __klp_resolve_kallsyms_relocs(klp_funcs,
+ ARRAY_SIZE(klp_funcs));
+ if (ret)
+ goto out;
+ }
+
+ ret = register_module_notifier(&livepatch_bsc1119947_sunrpc_module_nb);
+out:
+ mutex_unlock(&module_mutex);
+ return ret;
+}
+
+void livepatch_bsc1119947_sunrpc_cleanup(void)
+{
+ unregister_module_notifier(&livepatch_bsc1119947_sunrpc_module_nb);
+}
diff --git a/bsc1119947/livepatch_bsc1119947_sunrpc.h b/bsc1119947/livepatch_bsc1119947_sunrpc.h
new file mode 100644
index 0000000..6cce7d1
--- /dev/null
+++ b/bsc1119947/livepatch_bsc1119947_sunrpc.h
@@ -0,0 +1,15 @@
+#ifndef _LIVEPATCH_BSC1119947_SUNRPC_H
+#define _LIVEPATCH_BSC1119947_SUNRPC_H
+
+int livepatch_bsc1119947_sunrpc_init(void);
+void livepatch_bsc1119947_sunrpc_cleanup(void);
+
+
+struct svc_serv;
+struct rpc_rqst;
+struct svc_rqst;
+
+int klp_bc_svc_process(struct svc_serv *serv, struct rpc_rqst *req,
+ struct svc_rqst *rqstp);
+
+#endif /* _LIVEPATCH_BSC1119947_SUNRPC_H */
diff --git a/bsc1119947/patched_funcs.csv b/bsc1119947/patched_funcs.csv
new file mode 100644
index 0000000..7c70579
--- /dev/null
+++ b/bsc1119947/patched_funcs.csv
@@ -0,0 +1,4 @@
+sunrpc bc_svc_process klp_bc_svc_process
+auth_rpcgss svcauth_gss_accept klp_svcauth_gss_accept
+auth_rpcgss svcauth_gss_release klp_svcauth_gss_release
+nfsv4 nfs4_callback_compound klp_nfs4_callback_compound