Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

sunrpc: trim off trailing checksum before returning decrypted or integrity authenticated buffer

When GSSAPI integrity signatures are in use, or when we're using GSSAPI
privacy with the v2 token format, there is a trailing checksum on the
xdr_buf that is returned.

It's checked during the authentication stage, and afterward nothing
cares about it. Ordinarily, it's not a problem since the XDR code
generally ignores it, but it will be when we try to compute a checksum
over the buffer to help prevent XID collisions in the duplicate reply
cache.

Fix the code to trim off the checksums after verifying them. Note that
in unwrap_integ_data, we must avoid trying to reverify the checksum if
the request was deferred since it will no longer be present when it's
revisited.

Signed-off-by: Jeff Layton <jlayton@redhat.com>

authored by

Jeff Layton and committed by
J. Bruce Fields
4c190e2f de0b65ca

+52 -2
+1
include/linux/sunrpc/xdr.h
··· 152 152 extern void xdr_shift_buf(struct xdr_buf *, size_t); 153 153 extern void xdr_buf_from_iov(struct kvec *, struct xdr_buf *); 154 154 extern int xdr_buf_subsegment(struct xdr_buf *, struct xdr_buf *, unsigned int, unsigned int); 155 + extern void xdr_buf_trim(struct xdr_buf *, unsigned int); 155 156 extern int xdr_buf_read_netobj(struct xdr_buf *, struct xdr_netobj *, unsigned int); 156 157 extern int read_bytes_from_xdr_buf(struct xdr_buf *, unsigned int, void *, unsigned int); 157 158 extern int write_bytes_to_xdr_buf(struct xdr_buf *, unsigned int, void *, unsigned int);
+2
net/sunrpc/auth_gss/gss_krb5_wrap.c
··· 574 574 buf->head[0].iov_len -= GSS_KRB5_TOK_HDR_LEN + headskip; 575 575 buf->len -= GSS_KRB5_TOK_HDR_LEN + headskip; 576 576 577 + /* Trim off the checksum blob */ 578 + xdr_buf_trim(buf, GSS_KRB5_TOK_HDR_LEN + tailskip); 577 579 return GSS_S_COMPLETE; 578 580 } 579 581
+8 -2
net/sunrpc/auth_gss/svcauth_gss.c
··· 817 817 * The server uses base of head iovec as read pointer, while the 818 818 * client uses separate pointer. */ 819 819 static int 820 - unwrap_integ_data(struct xdr_buf *buf, u32 seq, struct gss_ctx *ctx) 820 + unwrap_integ_data(struct svc_rqst *rqstp, struct xdr_buf *buf, u32 seq, struct gss_ctx *ctx) 821 821 { 822 822 int stat = -EINVAL; 823 823 u32 integ_len, maj_stat; 824 824 struct xdr_netobj mic; 825 825 struct xdr_buf integ_buf; 826 + 827 + /* Did we already verify the signature on the original pass through? */ 828 + if (rqstp->rq_deferred) 829 + return 0; 826 830 827 831 integ_len = svc_getnl(&buf->head[0]); 828 832 if (integ_len & 3) ··· 850 846 goto out; 851 847 if (svc_getnl(&buf->head[0]) != seq) 852 848 goto out; 849 + /* trim off the mic at the end before returning */ 850 + xdr_buf_trim(buf, mic.len + 4); 853 851 stat = 0; 854 852 out: 855 853 kfree(mic.data); ··· 1196 1190 /* placeholders for length and seq. number: */ 1197 1191 svc_putnl(resv, 0); 1198 1192 svc_putnl(resv, 0); 1199 - if (unwrap_integ_data(&rqstp->rq_arg, 1193 + if (unwrap_integ_data(rqstp, &rqstp->rq_arg, 1200 1194 gc->gc_seq, rsci->mechctx)) 1201 1195 goto garbage_args; 1202 1196 break;
+41
net/sunrpc/xdr.c
··· 879 879 } 880 880 EXPORT_SYMBOL_GPL(xdr_buf_subsegment); 881 881 882 + /** 883 + * xdr_buf_trim - lop at most "len" bytes off the end of "buf" 884 + * @buf: buf to be trimmed 885 + * @len: number of bytes to reduce "buf" by 886 + * 887 + * Trim an xdr_buf by the given number of bytes by fixing up the lengths. Note 888 + * that it's possible that we'll trim less than that amount if the xdr_buf is 889 + * too small, or if (for instance) it's all in the head and the parser has 890 + * already read too far into it. 891 + */ 892 + void xdr_buf_trim(struct xdr_buf *buf, unsigned int len) 893 + { 894 + size_t cur; 895 + unsigned int trim = len; 896 + 897 + if (buf->tail[0].iov_len) { 898 + cur = min_t(size_t, buf->tail[0].iov_len, trim); 899 + buf->tail[0].iov_len -= cur; 900 + trim -= cur; 901 + if (!trim) 902 + goto fix_len; 903 + } 904 + 905 + if (buf->page_len) { 906 + cur = min_t(unsigned int, buf->page_len, trim); 907 + buf->page_len -= cur; 908 + trim -= cur; 909 + if (!trim) 910 + goto fix_len; 911 + } 912 + 913 + if (buf->head[0].iov_len) { 914 + cur = min_t(size_t, buf->head[0].iov_len, trim); 915 + buf->head[0].iov_len -= cur; 916 + trim -= cur; 917 + } 918 + fix_len: 919 + buf->len -= (len - trim); 920 + } 921 + EXPORT_SYMBOL_GPL(xdr_buf_trim); 922 + 882 923 static void __read_bytes_from_xdr_buf(struct xdr_buf *subbuf, void *obj, unsigned int len) 883 924 { 884 925 unsigned int this_len;