NFSD: Prevent a buffer overflow in svc_xprt_names()

The svc_xprt_names() function can overflow its buffer if it's so near
the end of the passed in buffer that the "name too long" string still
doesn't fit.  Of course, it could never tell if it was near the end
of the passed in buffer, since its only caller passes in zero as the
buffer length.

Let's make this API a little safer.

Change svc_xprt_names() so it *always* checks for a buffer overflow,
and change its only caller to pass in the correct buffer length.

If svc_xprt_names() does overflow its buffer, it now fails with an
ENAMETOOLONG errno, instead of trying to write a message at the end
of the buffer.  I don't like this much, but I can't figure out a clean
way that's always safe to return some of the names, *and* an
indication that the buffer was not long enough.

The displayed error when doing a 'cat /proc/fs/nfsd/portlist' is
"File name too long".

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu>
This commit is contained in:
Chuck Lever 2009-04-23 19:32:25 -04:00 committed by J. Bruce Fields
parent ea068bad27
commit 335c54bdc4
3 changed files with 41 additions and 19 deletions

View File

@ -918,7 +918,7 @@ static ssize_t __write_ports_names(char *buf)
{ {
if (nfsd_serv == NULL) if (nfsd_serv == NULL)
return 0; return 0;
return svc_xprt_names(nfsd_serv, buf, 0); return svc_xprt_names(nfsd_serv, buf, SIMPLE_TRANSACTION_LIMIT);
} }
/* /*

View File

@ -83,7 +83,7 @@ int svc_port_is_privileged(struct sockaddr *sin);
int svc_print_xprts(char *buf, int maxlen); int svc_print_xprts(char *buf, int maxlen);
struct svc_xprt *svc_find_xprt(struct svc_serv *serv, const char *xcl_name, struct svc_xprt *svc_find_xprt(struct svc_serv *serv, const char *xcl_name,
const sa_family_t af, const unsigned short port); const sa_family_t af, const unsigned short port);
int svc_xprt_names(struct svc_serv *serv, char *buf, int buflen); int svc_xprt_names(struct svc_serv *serv, char *buf, const int buflen);
static inline void svc_xprt_get(struct svc_xprt *xprt) static inline void svc_xprt_get(struct svc_xprt *xprt)
{ {

View File

@ -1098,36 +1098,58 @@ struct svc_xprt *svc_find_xprt(struct svc_serv *serv, const char *xcl_name,
} }
EXPORT_SYMBOL_GPL(svc_find_xprt); EXPORT_SYMBOL_GPL(svc_find_xprt);
/* static int svc_one_xprt_name(const struct svc_xprt *xprt,
* Format a buffer with a list of the active transports. A zero for char *pos, int remaining)
* the buflen parameter disables target buffer overflow checking. {
int len;
len = snprintf(pos, remaining, "%s %u\n",
xprt->xpt_class->xcl_name,
svc_xprt_local_port(xprt));
if (len >= remaining)
return -ENAMETOOLONG;
return len;
}
/**
* svc_xprt_names - format a buffer with a list of transport names
* @serv: pointer to an RPC service
* @buf: pointer to a buffer to be filled in
* @buflen: length of buffer to be filled in
*
* Fills in @buf with a string containing a list of transport names,
* each name terminated with '\n'.
*
* Returns positive length of the filled-in string on success; otherwise
* a negative errno value is returned if an error occurs.
*/ */
int svc_xprt_names(struct svc_serv *serv, char *buf, int buflen) int svc_xprt_names(struct svc_serv *serv, char *buf, const int buflen)
{ {
struct svc_xprt *xprt; struct svc_xprt *xprt;
char xprt_str[64]; int len, totlen;
int totlen = 0; char *pos;
int len;
/* Sanity check args */ /* Sanity check args */
if (!serv) if (!serv)
return 0; return 0;
spin_lock_bh(&serv->sv_lock); spin_lock_bh(&serv->sv_lock);
pos = buf;
totlen = 0;
list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) { list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) {
len = snprintf(xprt_str, sizeof(xprt_str), len = svc_one_xprt_name(xprt, pos, buflen - totlen);
"%s %d\n", xprt->xpt_class->xcl_name, if (len < 0) {
svc_xprt_local_port(xprt)); *buf = '\0';
/* If the string was truncated, replace with error string */ totlen = len;
if (len >= sizeof(xprt_str)) }
strcpy(xprt_str, "name-too-long\n"); if (len <= 0)
/* Don't overflow buffer */
len = strlen(xprt_str);
if (buflen && (len + totlen >= buflen))
break; break;
strcpy(buf+totlen, xprt_str);
pos += len;
totlen += len; totlen += len;
} }
spin_unlock_bh(&serv->sv_lock); spin_unlock_bh(&serv->sv_lock);
return totlen; return totlen;
} }