SUNRPC: auth_gss: fix memory leaks in XDR decoding error paths
Subsystems (4)
Chuck Lever
maintainerPATCH 1/1 SUNRPC: auth_gss: fix memory leaks in XDR decoding error paths
From: Chuck Lever <[email protected]>
The gssx_dec_ctx(), gssx_dec_status(), and gssx_dec_name() functions allocate memory via gssx_dec_buffer(), which calls kmemdup(). When a subsequent decode operation fails, these functions return immediately without freeing previously allocated buffers, causing memory leaks.
The leak in gssx_dec_ctx() is particularly relevant because the caller (gssp_accept_sec_context_upcall) initializes several buffer length fields to non-zero values, resulting in memory allocation:
struct gssx_ctx rctxh = {
.exported_context_token.len = GSSX_max_output_handle_sz, .mech.len = GSS_OID_MAX_LEN, .src_name.display_name.len = GSSX_max_princ_sz, .targ_name.display_name.len = GSSX_max_princ_sz
};
If, for example, gssx_dec_name() succeeds for src_name but fails for targ_name, the memory allocated for exported_context_token, mech, and src_name.display_name remains unreferenced and cannot be reclaimed.
Add error handling with goto-based cleanup to free any previously allocated buffers before returning an error.
--- net/sunrpc/auth_gss/gss_rpc_xdr.c | 82 ++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 18 deletions(-)
net/sunrpc/auth_gss/gss_rpc_xdr.c +64 -18
@@ -320,29 +320,47 @@ static int gssx_dec_status(struct xdr_stream *xdr,/* status->minor_status */ p =xdr_inline_decode (xdr,8 ); -if (unlikely (p ==NULL )) -return -ENOSPC ; +if (unlikely (p ==NULL )) { + err = -ENOSPC ; + goto out_free_mech; + } p =xdr_decode_hyper (p, &status->minor_status);/* status->major_status_string */ err =gssx_dec_buffer (xdr, &status->major_status_string);if (err) -return err; + goto out_free_mech;/* status->minor_status_string */ err =gssx_dec_buffer (xdr, &status->minor_status_string);if (err) -return err; + goto out_free_major_status_string;/* status->server_ctx */ err =gssx_dec_buffer (xdr, &status->server_ctx);if (err) -return err; + goto out_free_minor_status_string;/* we assume we have no options for now, so simply consume them */ /* status->options */ err =dummy_dec_opt_array (xdr, &status->options); +if (err) + goto out_free_server_ctx; +return 0 ; + +out_free_server_ctx: +kfree (status->server_ctx.data); + status->server_ctx.data =NULL ; +out_free_minor_status_string: +kfree (status->minor_status_string.data); + status->minor_status_string.data =NULL ; +out_free_major_status_string: +kfree (status->major_status_string.data); + status->major_status_string.data =NULL ; +out_free_mech: +kfree (status->mech.data); + status->mech.data =NULL ;return err; }
@@ -505,28 +523,35 @@ static int gssx_dec_name(struct xdr_stream *xdr,/* name->name_type */ err =gssx_dec_buffer (xdr, &dummy_netobj);if (err) -return err; + goto out_free_display_name;/* name->exported_name */ err =gssx_dec_buffer (xdr, &dummy_netobj);if (err) -return err; + goto out_free_display_name;/* name->exported_composite_name */ err =gssx_dec_buffer (xdr, &dummy_netobj);if (err) -return err; + goto out_free_display_name;/* we assume we have no attributes for now, so simply consume them */ /* name->name_attributes */ err =dummy_dec_nameattr_array (xdr, &dummy_name_attr_array);if (err) -return err; + goto out_free_display_name;/* we assume we have no options for now, so simply consume them */ /* name->extensions */ err =dummy_dec_opt_array (xdr, &dummy_option_array); +if (err) + goto out_free_display_name; +return 0 ; + +out_free_display_name: +kfree (name->display_name.data); + name->display_name.data =NULL ;return err; }
@@ -649,32 +674,34 @@ static int gssx_dec_ctx(struct xdr_stream *xdr,/* ctx->state */ err =gssx_dec_buffer (xdr, &ctx->state);if (err) -return err; + goto out_free_exported_context_token;/* ctx->need_release */ err =gssx_dec_bool (xdr, &ctx->need_release);if (err) -return err; + goto out_free_state;/* ctx->mech */ err =gssx_dec_buffer (xdr, &ctx->mech);if (err) -return err; + goto out_free_state;/* ctx->src_name */ err =gssx_dec_name (xdr, &ctx->src_name);if (err) -return err; + goto out_free_mech;/* ctx->targ_name */ err =gssx_dec_name (xdr, &ctx->targ_name);if (err) -return err; + goto out_free_src_name;/* ctx->lifetime */ p =xdr_inline_decode (xdr,8 +8 ); -if (unlikely (p ==NULL )) -return -ENOSPC ; +if (unlikely (p ==NULL )) { + err = -ENOSPC ; + goto out_free_targ_name; + } p =xdr_decode_hyper (p, &ctx->lifetime);/* ctx->ctx_flags */
@@ -683,17 +710,36 @@ static int gssx_dec_ctx(struct xdr_stream *xdr,/* ctx->locally_initiated */ err =gssx_dec_bool (xdr, &ctx->locally_initiated);if (err) -return err; + goto out_free_targ_name;/* ctx->open */ err =gssx_dec_bool (xdr, &ctx->open);if (err) -return err; + goto out_free_targ_name;/* we assume we have no options for now, so simply consume them */ /* ctx->options */ err =dummy_dec_opt_array (xdr, &ctx->options); +if (err) + goto out_free_targ_name; +return 0 ; + +out_free_targ_name: +kfree (ctx->targ_name.display_name.data); + ctx->targ_name.display_name.data =NULL ; +out_free_src_name: +kfree (ctx->src_name.display_name.data); + ctx->src_name.display_name.data =NULL ; +out_free_mech: +kfree (ctx->mech.data); + ctx->mech.data =NULL ; +out_free_state: +kfree (ctx->state.data); + ctx->state.data =NULL ; +out_free_exported_context_token: +kfree (ctx->exported_context_token.data); + ctx->exported_context_token.data =NULL ;return err; }
Signature · 1 line
2.52.0
Quoted 205 lines · Chuck Lever
From: Chuck Lever <[email protected]>
The
gssx_dec_ctx(),gssx_dec_status(), andgssx_dec_name()functions allocate memory viagssx_dec_buffer(), which callskmemdup(). When a subsequent decode operation fails, these functions return immediately without freeing previously allocated buffers, causing memory leaks.The leak in
gssx_dec_ctx()is particularly relevant because the caller (gssp_accept_sec_context_upcall) initializes several buffer length fields to non-zero values, resulting in memory allocation:struct
gssx_ctxrctxh = {
.exported_context_token.len =GSSX_max_output_handle_sz, .mech.len =GSS_OID_MAX_LEN, .src_name.display_name.len =GSSX_max_princ_sz, .targ_name.display_name.len =GSSX_max_princ_sz
};If, for example,
gssx_dec_name()succeeds forsrc_namebut fails fortarg_name, the memory allocated forexported_context_token, mech, and src_name.display_name remains unreferenced and cannot be reclaimed.Add error handling with goto-based cleanup to free any previously allocated buffers before returning an error.
Reported-by Xingjing Deng <[email protected]>Closes https://lore.kernel.org/linux-nfs/CAK+ZN9qttsFDu6h1FoqGadXjMx1QXqPMoYQ=6O9RY4SxVTvKng@mail.gmail.com/ open hereFixes 1d658336b05f ("SUNRPC: Add RPC based upcall mechanism for RPCGSS auth")Signed-off-by Chuck Lever <[email protected]>--- net/sunrpc/auth_gss/gss_rpc_xdr.c | 82 ++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 18 deletions(-)net/sunrpc/auth_gss/gss_rpc_xdr.c +57
-18@@ -320,29 +320,47 @@ static int gssx_dec_status(struct xdr_stream *xdr,/* status->minor_status */ p =xdr_inline_decode (xdr,8 ); -if (unlikely (p ==NULL )) -return -ENOSPC ; +if (unlikely (p ==NULL )) { + err = -ENOSPC ; + goto out_free_mech; + } p =xdr_decode_hyper (p, &status->minor_status);/* status->major_status_string */ err =gssx_dec_buffer (xdr, &status->major_status_string);if (err) -return err; + goto out_free_mech;/* status->minor_status_string */ err =gssx_dec_buffer (xdr, &status->minor_status_string);if (err) -return err; + goto out_free_major_status_string;/* status->server_ctx */ err =gssx_dec_buffer (xdr, &status->server_ctx);if (err) -return err; + goto out_free_minor_status_string;/* we assume we have no options for now, so simply consume them */ /* status->options */ err =dummy_dec_opt_array (xdr, &status->options); +if (err) + goto out_free_server_ctx; +return 0 ; + +out_free_server_ctx: +kfree (status->server_ctx.data); + status->server_ctx.data =NULL ; +out_free_minor_status_string: +kfree (status->minor_status_string.data); + status->minor_status_string.data =NULL ; +out_free_major_status_string: +kfree (status->major_status_string.data); + status->major_status_string.data =NULL ; +out_free_mech: +kfree (status->mech.data); + status->mech.data =NULL ;return err; }@@ -505,28 +523,35 @@ static int gssx_dec_name(struct xdr_stream *xdr,/* name->name_type */ err =gssx_dec_buffer (xdr, &dummy_netobj);if (err) -return err; + goto out_free_display_name;/* name->exported_name */ err =gssx_dec_buffer (xdr, &dummy_netobj);if (err) -return err; + goto out_free_display_name;/* name->exported_composite_name */ err =gssx_dec_buffer (xdr, &dummy_netobj);if (err) -return err; + goto out_free_display_name;/* we assume we have no attributes for now, so simply consume them */ /* name->name_attributes */ err =dummy_dec_nameattr_array (xdr, &dummy_name_attr_array);if (err) -return err; + goto out_free_display_name;/* we assume we have no options for now, so simply consume them */ /* name->extensions */ err =dummy_dec_opt_array (xdr, &dummy_option_array); +if (err) + goto out_free_display_name; +return 0 ; + +out_free_display_name: +kfree (name->display_name.data); + name->display_name.data =NULL ;return err; }@@ -649,32 +674,34 @@ static int gssx_dec_ctx(struct xdr_stream *xdr,/* ctx->state */ err =gssx_dec_buffer (xdr, &ctx->state);if (err) -return err; + goto out_free_exported_context_token;/* ctx->need_release */ err =gssx_dec_bool (xdr, &ctx->need_release);if (err) -return err; + goto out_free_state;/* ctx->mech */ err =gssx_dec_buffer (xdr, &ctx->mech);if (err) -return err; + goto out_free_state;/* ctx->src_name */ err =gssx_dec_name (xdr, &ctx->src_name);if (err) -return err; + goto out_free_mech;/* ctx->targ_name */ err =gssx_dec_name (xdr, &ctx->targ_name);if (err) -return err; + goto out_free_src_name;/* ctx->lifetime */ p =xdr_inline_decode (xdr,8 +8 ); -if (unlikely (p ==NULL )) -return -ENOSPC ; +if (unlikely (p ==NULL )) { + err = -ENOSPC ; + goto out_free_targ_name; + } p =xdr_decode_hyper (p, &ctx->lifetime);/* ctx->ctx_flags */ @@ -683,17 +710,36 @@ static int gssx_dec_ctx(struct xdr_stream *xdr,/* ctx->locally_initiated */ err =gssx_dec_bool (xdr, &ctx->locally_initiated);if (err) -return err; + goto out_free_targ_name;/* ctx->open */ err =gssx_dec_bool (xdr, &ctx->open);if (err) -return err; + goto out_free_targ_name;/* we assume we have no options for now, so simply consume them */ /* ctx->options */ err =dummy_dec_opt_array (xdr, &ctx->options); +if (err) + goto out_free_targ_name; +return 0 ; + +out_free_targ_name: +kfree (ctx->targ_name.display_name.data); + ctx->targ_name.display_name.data =NULL ; +out_free_src_name: +kfree (ctx->src_name.display_name.data); + ctx->src_name.display_name.data =NULL ; +out_free_mech: +kfree (ctx->mech.data);
net/sunrpc/auth_gss/gss_rpc_xdr.c static int gssx_dec_ctx(struct xdr_stream *xdr, · line 745
Quoted 10 lines
net/sunrpc/auth_gss/gss_rpc_xdr.c +7
-0+ ctx->mech.data =NULL ; +out_free_state: +kfree (ctx->state.data); + ctx->state.data =NULL ; +out_free_exported_context_token: +kfree (ctx->exported_context_token.data); + ctx->exported_context_token.data =NULL ;return err; }