Skip to content

Commit 0f51624

Browse files
committed
NFSD: Watch for rq_pages bounds checking errors in nfsd_splice_actor()
There have been several bugs over the years where the NFSD splice actor has attempted to write outside the rq_pages array. This is a "should never happen" condition, but if for some reason the pipe splice actor should attempt to walk past the end of rq_pages, it needs to terminate the READ operation to prevent corruption of the pointer addresses in the fields just beyond the array. A server crash is thus prevented. Since the code is not behaving, the READ operation returns -EIO to the client. None of the READ payload data can be trusted if the splice actor isn't operating as expected. Suggested-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Chuck Lever <chuck.lever@oracle.com> Reviewed-by: Jeff Layton <jlayton@kernel.org>
1 parent 376bcd9 commit 0f51624

4 files changed

Lines changed: 45 additions & 3 deletions

File tree

fs/nfsd/vfs.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,9 @@ nfsd_open_verified(struct svc_rqst *rqstp, struct svc_fh *fhp, int may_flags,
930930
* Grab and keep cached pages associated with a file in the svc_rqst
931931
* so that they can be passed to the network sendmsg/sendpage routines
932932
* directly. They will be released after the sending has completed.
933+
*
934+
* Return values: Number of bytes consumed, or -EIO if there are no
935+
* remaining pages in rqstp->rq_pages.
933936
*/
934937
static int
935938
nfsd_splice_actor(struct pipe_inode_info *pipe, struct pipe_buffer *buf,
@@ -948,7 +951,8 @@ nfsd_splice_actor(struct pipe_inode_info *pipe, struct pipe_buffer *buf,
948951
*/
949952
if (page == *(rqstp->rq_next_page - 1))
950953
continue;
951-
svc_rqst_replace_page(rqstp, page);
954+
if (unlikely(!svc_rqst_replace_page(rqstp, page)))
955+
return -EIO;
952956
}
953957
if (rqstp->rq_res.page_len == 0) // first call
954958
rqstp->rq_res.page_base = offset % PAGE_SIZE;

include/linux/sunrpc/svc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ struct svc_serv *svc_create(struct svc_program *, unsigned int,
422422
int (*threadfn)(void *data));
423423
struct svc_rqst *svc_rqst_alloc(struct svc_serv *serv,
424424
struct svc_pool *pool, int node);
425-
void svc_rqst_replace_page(struct svc_rqst *rqstp,
425+
bool svc_rqst_replace_page(struct svc_rqst *rqstp,
426426
struct page *page);
427427
void svc_rqst_free(struct svc_rqst *);
428428
void svc_exit_thread(struct svc_rqst *);

include/trace/events/sunrpc.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,6 +1790,31 @@ DEFINE_EVENT(svc_rqst_status, svc_send,
17901790
TP_PROTO(const struct svc_rqst *rqst, int status),
17911791
TP_ARGS(rqst, status));
17921792

1793+
TRACE_EVENT(svc_replace_page_err,
1794+
TP_PROTO(const struct svc_rqst *rqst),
1795+
1796+
TP_ARGS(rqst),
1797+
TP_STRUCT__entry(
1798+
SVC_RQST_ENDPOINT_FIELDS(rqst)
1799+
1800+
__field(const void *, begin)
1801+
__field(const void *, respages)
1802+
__field(const void *, nextpage)
1803+
),
1804+
1805+
TP_fast_assign(
1806+
SVC_RQST_ENDPOINT_ASSIGNMENTS(rqst);
1807+
1808+
__entry->begin = rqst->rq_pages;
1809+
__entry->respages = rqst->rq_respages;
1810+
__entry->nextpage = rqst->rq_next_page;
1811+
),
1812+
1813+
TP_printk(SVC_RQST_ENDPOINT_FORMAT " begin=%p respages=%p nextpage=%p",
1814+
SVC_RQST_ENDPOINT_VARARGS,
1815+
__entry->begin, __entry->respages, __entry->nextpage)
1816+
);
1817+
17931818
TRACE_EVENT(svc_stats_latency,
17941819
TP_PROTO(
17951820
const struct svc_rqst *rqst

net/sunrpc/svc.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,9 +842,21 @@ EXPORT_SYMBOL_GPL(svc_set_num_threads);
842842
*
843843
* When replacing a page in rq_pages, batch the release of the
844844
* replaced pages to avoid hammering the page allocator.
845+
*
846+
* Return values:
847+
* %true: page replaced
848+
* %false: array bounds checking failed
845849
*/
846-
void svc_rqst_replace_page(struct svc_rqst *rqstp, struct page *page)
850+
bool svc_rqst_replace_page(struct svc_rqst *rqstp, struct page *page)
847851
{
852+
struct page **begin = rqstp->rq_pages;
853+
struct page **end = &rqstp->rq_pages[RPCSVC_MAXPAGES];
854+
855+
if (unlikely(rqstp->rq_next_page < begin || rqstp->rq_next_page > end)) {
856+
trace_svc_replace_page_err(rqstp);
857+
return false;
858+
}
859+
848860
if (*rqstp->rq_next_page) {
849861
if (!pagevec_space(&rqstp->rq_pvec))
850862
__pagevec_release(&rqstp->rq_pvec);
@@ -853,6 +865,7 @@ void svc_rqst_replace_page(struct svc_rqst *rqstp, struct page *page)
853865

854866
get_page(page);
855867
*(rqstp->rq_next_page++) = page;
868+
return true;
856869
}
857870
EXPORT_SYMBOL_GPL(svc_rqst_replace_page);
858871

0 commit comments

Comments
 (0)