Plug a memory leak and potential NULL-pointer dereference introduced in r331214.

Each TCP connection that uses the system default cc_newreno(4) congestion
control algorithm module leaks a "struct newreno" (8 bytes of memory) at
connection initialisation time. The NULL-pointer dereference is only germane
when using the ABE feature, which is disabled by default.

While at it:

- Defer the allocation of memory until it is actually needed given that ABE is
  optional and disabled by default.

- Document the ENOMEM errno in getsockopt(2)/setsockopt(2).

- Document ENOMEM and ENOBUFS in tcp(4) as being synonymous given that they are
  used interchangeably throughout the code.

- Fix a few other nits also accidentally omitted from the original patch.

Reported by:	Harsh Jain on freebsd-net@
Tested by:	tjh@
Differential Revision:	https://reviews.freebsd.org/D15358
This commit is contained in:
Lawrence Stewart 2018-05-17 02:46:27 +00:00
parent eff62dba61
commit 9891578a40
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=333699
3 changed files with 47 additions and 27 deletions

View File

@ -28,7 +28,7 @@
.\" @(#)getsockopt.2 8.4 (Berkeley) 5/2/95 .\" @(#)getsockopt.2 8.4 (Berkeley) 5/2/95
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd January 18, 2017 .Dd May 9, 2018
.Dt GETSOCKOPT 2 .Dt GETSOCKOPT 2
.Os .Os
.Sh NAME .Sh NAME
@ -548,6 +548,8 @@ is not in a valid part of the process address space.
Installing an Installing an
.Xr accept_filter 9 .Xr accept_filter 9
on a non-listening socket was attempted. on a non-listening socket was attempted.
.It Bq Er ENOMEM
A memory allocation failed that was required to service the request.
.El .El
.Sh SEE ALSO .Sh SEE ALSO
.Xr ioctl 2 , .Xr ioctl 2 ,

View File

@ -34,7 +34,7 @@
.\" From: @(#)tcp.4 8.1 (Berkeley) 6/5/93 .\" From: @(#)tcp.4 8.1 (Berkeley) 6/5/93
.\" $FreeBSD$ .\" $FreeBSD$
.\" .\"
.Dd February 6, 2017 .Dd May 9, 2018
.Dt TCP 4 .Dt TCP 4
.Os .Os
.Sh NAME .Sh NAME
@ -599,7 +599,7 @@ A socket operation may fail with one of the following errors returned:
.It Bq Er EISCONN .It Bq Er EISCONN
when trying to establish a connection on a socket which when trying to establish a connection on a socket which
already has one; already has one;
.It Bq Er ENOBUFS .It Bo Er ENOBUFS Bc or Bo Er ENOMEM Bc
when the system runs out of memory for when the system runs out of memory for
an internal data structure; an internal data structure;
.It Bq Er ETIMEDOUT .It Bq Er ETIMEDOUT

View File

@ -81,7 +81,7 @@ static MALLOC_DEFINE(M_NEWRENO, "newreno data",
#define CAST_PTR_INT(X) (*((int*)(X))) #define CAST_PTR_INT(X) (*((int*)(X)))
static int newreno_cb_init(struct cc_var *ccv); static void newreno_cb_destroy(struct cc_var *ccv);
static void newreno_ack_received(struct cc_var *ccv, uint16_t type); static void newreno_ack_received(struct cc_var *ccv, uint16_t type);
static void newreno_after_idle(struct cc_var *ccv); static void newreno_after_idle(struct cc_var *ccv);
static void newreno_cong_signal(struct cc_var *ccv, uint32_t type); static void newreno_cong_signal(struct cc_var *ccv, uint32_t type);
@ -95,7 +95,7 @@ static VNET_DEFINE(uint32_t, newreno_beta_ecn) = 80;
struct cc_algo newreno_cc_algo = { struct cc_algo newreno_cc_algo = {
.name = "newreno", .name = "newreno",
.cb_init = newreno_cb_init, .cb_destroy = newreno_cb_destroy,
.ack_received = newreno_ack_received, .ack_received = newreno_ack_received,
.after_idle = newreno_after_idle, .after_idle = newreno_after_idle,
.cong_signal = newreno_cong_signal, .cong_signal = newreno_cong_signal,
@ -108,18 +108,28 @@ struct newreno {
uint32_t beta_ecn; uint32_t beta_ecn;
}; };
int static inline struct newreno *
newreno_cb_init(struct cc_var *ccv) newreno_malloc(struct cc_var *ccv)
{ {
struct newreno *nreno; struct newreno *nreno;
nreno = malloc(sizeof(struct newreno), M_NEWRENO, M_NOWAIT|M_ZERO); nreno = malloc(sizeof(struct newreno), M_NEWRENO, M_NOWAIT);
if (nreno != NULL) { if (nreno != NULL) {
/* NB: nreno is not zeroed, so initialise all fields. */
nreno->beta = V_newreno_beta; nreno->beta = V_newreno_beta;
nreno->beta_ecn = V_newreno_beta_ecn; nreno->beta_ecn = V_newreno_beta_ecn;
ccv->cc_data = nreno;
} }
return (0); return (nreno);
}
static void
newreno_cb_destroy(struct cc_var *ccv)
{
if (ccv->cc_data != NULL)
free(ccv->cc_data, M_NEWRENO);
} }
static void static void
@ -224,20 +234,18 @@ static void
newreno_cong_signal(struct cc_var *ccv, uint32_t type) newreno_cong_signal(struct cc_var *ccv, uint32_t type)
{ {
struct newreno *nreno; struct newreno *nreno;
uint32_t cwin, factor; uint32_t beta, beta_ecn, cwin, factor;
u_int mss; u_int mss;
factor = V_newreno_beta;
nreno = ccv->cc_data;
if (nreno != NULL) {
if (V_cc_do_abe)
factor = (type == CC_ECN ? nreno->beta_ecn: nreno->beta);
else
factor = nreno->beta;
}
cwin = CCV(ccv, snd_cwnd); cwin = CCV(ccv, snd_cwnd);
mss = CCV(ccv, t_maxseg); mss = CCV(ccv, t_maxseg);
nreno = ccv->cc_data;
beta = (nreno == NULL) ? V_newreno_beta : nreno->beta;
beta_ecn = (nreno == NULL) ? V_newreno_beta_ecn : nreno->beta_ecn;
if (V_cc_do_abe && type == CC_ECN)
factor = beta_ecn;
else
factor = beta;
/* Catch algos which mistakenly leak private signal types. */ /* Catch algos which mistakenly leak private signal types. */
KASSERT((type & CC_SIGPRIVMASK) == 0, KASSERT((type & CC_SIGPRIVMASK) == 0,
@ -253,8 +261,8 @@ newreno_cong_signal(struct cc_var *ccv, uint32_t type)
V_cc_do_abe && V_cc_abe_frlossreduce)) { V_cc_do_abe && V_cc_abe_frlossreduce)) {
CCV(ccv, snd_ssthresh) = CCV(ccv, snd_ssthresh) =
((uint64_t)CCV(ccv, snd_ssthresh) * ((uint64_t)CCV(ccv, snd_ssthresh) *
(uint64_t)nreno->beta) / (uint64_t)beta) /
(100ULL * (uint64_t)nreno->beta_ecn); (100ULL * (uint64_t)beta_ecn);
} }
if (!IN_CONGRECOVERY(CCV(ccv, t_flags))) if (!IN_CONGRECOVERY(CCV(ccv, t_flags)))
CCV(ccv, snd_ssthresh) = cwin; CCV(ccv, snd_ssthresh) = cwin;
@ -278,7 +286,6 @@ static void
newreno_post_recovery(struct cc_var *ccv) newreno_post_recovery(struct cc_var *ccv)
{ {
int pipe; int pipe;
pipe = 0;
if (IN_FASTRECOVERY(CCV(ccv, t_flags))) { if (IN_FASTRECOVERY(CCV(ccv, t_flags))) {
/* /*
@ -302,7 +309,7 @@ newreno_post_recovery(struct cc_var *ccv)
} }
} }
int static int
newreno_ctl_output(struct cc_var *ccv, struct sockopt *sopt, void *buf) newreno_ctl_output(struct cc_var *ccv, struct sockopt *sopt, void *buf)
{ {
struct newreno *nreno; struct newreno *nreno;
@ -313,9 +320,15 @@ newreno_ctl_output(struct cc_var *ccv, struct sockopt *sopt, void *buf)
nreno = ccv->cc_data; nreno = ccv->cc_data;
opt = buf; opt = buf;
switch (sopt->sopt_dir) { switch (sopt->sopt_dir) {
case SOPT_SET: case SOPT_SET:
/* We cannot set without cc_data memory. */
if (nreno == NULL) {
nreno = newreno_malloc(ccv);
if (nreno == NULL)
return (ENOMEM);
}
switch (opt->name) { switch (opt->name) {
case CC_NEWRENO_BETA: case CC_NEWRENO_BETA:
nreno->beta = opt->val; nreno->beta = opt->val;
@ -328,17 +341,21 @@ newreno_ctl_output(struct cc_var *ccv, struct sockopt *sopt, void *buf)
default: default:
return (ENOPROTOOPT); return (ENOPROTOOPT);
} }
break;
case SOPT_GET: case SOPT_GET:
switch (opt->name) { switch (opt->name) {
case CC_NEWRENO_BETA: case CC_NEWRENO_BETA:
opt->val = nreno->beta; opt->val = (nreno == NULL) ?
V_newreno_beta : nreno->beta;
break; break;
case CC_NEWRENO_BETA_ECN: case CC_NEWRENO_BETA_ECN:
opt->val = nreno->beta_ecn; opt->val = (nreno == NULL) ?
V_newreno_beta_ecn : nreno->beta_ecn;
break; break;
default: default:
return (ENOPROTOOPT); return (ENOPROTOOPT);
} }
break;
default: default:
return (EINVAL); return (EINVAL);
} }
@ -349,6 +366,7 @@ newreno_ctl_output(struct cc_var *ccv, struct sockopt *sopt, void *buf)
static int static int
newreno_beta_handler(SYSCTL_HANDLER_ARGS) newreno_beta_handler(SYSCTL_HANDLER_ARGS)
{ {
if (req->newptr != NULL ) { if (req->newptr != NULL ) {
if (arg1 == &VNET_NAME(newreno_beta_ecn) && !V_cc_do_abe) if (arg1 == &VNET_NAME(newreno_beta_ecn) && !V_cc_do_abe)
return (EACCES); return (EACCES);