[PATCH 1 of 2] Core: avoid calling memcpy() in edge cases
Maxim Dounin
mdounin at mdounin.ru
Mon Dec 4 03:16:26 UTC 2023
Hello!
On Fri, Oct 27, 2023 at 02:58:44PM +0300, Vladimir Homutov via nginx-devel wrote:
> Patch subject is complete summary.
>
>
> src/core/ngx_cycle.c | 10 ++++++----
> src/core/ngx_resolver.c | 2 +-
> src/core/ngx_string.c | 15 +++++++++++++++
> src/http/modules/ngx_http_proxy_module.c | 4 ++--
> src/http/ngx_http_file_cache.c | 4 +++-
> src/http/ngx_http_variables.c | 3 +++
> src/mail/ngx_mail_auth_http_module.c | 12 +++++++++---
> src/stream/ngx_stream_script.c | 4 +++-
> 8 files changed, 42 insertions(+), 12 deletions(-)
>
>
> # HG changeset patch
> # User Vladimir Khomutov <vl at wbsrv.ru>
> # Date 1698407658 -10800
> # Fri Oct 27 14:54:18 2023 +0300
> # Node ID ef9f124b156aff0e9f66057e438af835bd7a60d2
> # Parent ea1f29c2010cda4940b741976f103d547308815a
> Core: avoid calling memcpy() in edge cases.
>
> diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c
> --- a/src/core/ngx_cycle.c
> +++ b/src/core/ngx_cycle.c
> @@ -115,10 +115,12 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
> old_cycle->conf_file.len + 1);
>
> cycle->conf_param.len = old_cycle->conf_param.len;
> - cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
> - if (cycle->conf_param.data == NULL) {
> - ngx_destroy_pool(pool);
> - return NULL;
> + if (cycle->conf_param.len) {
> + cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
> + if (cycle->conf_param.data == NULL) {
> + ngx_destroy_pool(pool);
> + return NULL;
> + }
> }
>
>
> diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c
> --- a/src/core/ngx_resolver.c
> +++ b/src/core/ngx_resolver.c
> @@ -4206,7 +4206,7 @@ ngx_resolver_dup(ngx_resolver_t *r, void
>
> dst = ngx_resolver_alloc(r, size);
>
> - if (dst == NULL) {
> + if (dst == NULL || size == 0 || src == NULL) {
> return dst;
> }
>
This looks simply wrong: ngx_resolver_dup() with src == NULL and
with size != 0 should dereference the NULL pointer and segfault.
Also, I can't say I'm happy with allocation error handling mixed
with (size == 0) special case handling.
> diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c
> --- a/src/core/ngx_string.c
> +++ b/src/core/ngx_string.c
> @@ -252,6 +252,11 @@ ngx_vslprintf(u_char *buf, u_char *last,
> case 'V':
> v = va_arg(args, ngx_str_t *);
>
> + if (v->len == 0 || v->data == NULL) {
> + fmt++;
> + continue;
> + }
> +
> buf = ngx_sprintf_str(buf, last, v->data, v->len, hex);
> fmt++;
>
> @@ -260,6 +265,11 @@ ngx_vslprintf(u_char *buf, u_char *last,
> case 'v':
> vv = va_arg(args, ngx_variable_value_t *);
>
> + if (vv->len == 0 || vv->data == NULL) {
> + fmt++;
> + continue;
> + }
> +
> buf = ngx_sprintf_str(buf, last, vv->data, vv->len, hex);
> fmt++;
>
> @@ -268,6 +278,11 @@ ngx_vslprintf(u_char *buf, u_char *last,
> case 's':
> p = va_arg(args, u_char *);
>
> + if (slen == 0 || p == NULL) {
> + fmt++;
> + continue;
> + }
> +
> buf = ngx_sprintf_str(buf, last, p, slen, hex);
> fmt++;
>
I tend to think that these should be handled in ngx_sprintf_str()
or in ngx_cpymem(), if at all, see below.
> diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c
> --- a/src/http/modules/ngx_http_proxy_module.c
> +++ b/src/http/modules/ngx_http_proxy_module.c
> @@ -1205,7 +1205,7 @@ ngx_http_proxy_create_key(ngx_http_reque
>
> key->data = p;
>
> - if (r->valid_location) {
> + if (r->valid_location && ctx->vars.uri.len) {
> p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len);
> }
>
> @@ -1422,7 +1422,7 @@ ngx_http_proxy_create_request(ngx_http_r
> b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len);
>
> } else {
> - if (r->valid_location) {
> + if (r->valid_location && ctx->vars.uri.len) {
> b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
> }
>
> diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c
> --- a/src/http/ngx_http_file_cache.c
> +++ b/src/http/ngx_http_file_cache.c
> @@ -1270,7 +1270,9 @@ ngx_http_file_cache_set_header(ngx_http_
>
> if (c->etag.len <= NGX_HTTP_CACHE_ETAG_LEN) {
> h->etag_len = (u_char) c->etag.len;
> - ngx_memcpy(h->etag, c->etag.data, c->etag.len);
> + if (c->etag.len) {
> + ngx_memcpy(h->etag, c->etag.data, c->etag.len);
> + }
> }
>
> if (c->vary.len) {
> diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c
> --- a/src/http/ngx_http_variables.c
> +++ b/src/http/ngx_http_variables.c
> @@ -2157,6 +2157,9 @@ ngx_http_variable_request_body(ngx_http_
>
> for ( /* void */ ; cl; cl = cl->next) {
> buf = cl->buf;
> + if (buf->last == buf->pos) {
> + continue;
> + }
> p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
> }
>
> diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c
> --- a/src/mail/ngx_mail_auth_http_module.c
> +++ b/src/mail/ngx_mail_auth_http_module.c
> @@ -1314,11 +1314,15 @@ ngx_mail_auth_http_create_request(ngx_ma
> *b->last++ = CR; *b->last++ = LF;
>
> b->last = ngx_cpymem(b->last, "Auth-User: ", sizeof("Auth-User: ") - 1);
> - b->last = ngx_copy(b->last, login.data, login.len);
> + if (login.len) {
> + b->last = ngx_copy(b->last, login.data, login.len);
> + }
> *b->last++ = CR; *b->last++ = LF;
>
> b->last = ngx_cpymem(b->last, "Auth-Pass: ", sizeof("Auth-Pass: ") - 1);
> - b->last = ngx_copy(b->last, passwd.data, passwd.len);
> + if (passwd.len) {
> + b->last = ngx_copy(b->last, passwd.data, passwd.len);
> + }
> *b->last++ = CR; *b->last++ = LF;
>
> if (s->auth_method != NGX_MAIL_AUTH_PLAIN && s->salt.len) {
> @@ -1375,7 +1379,9 @@ ngx_mail_auth_http_create_request(ngx_ma
>
> b->last = ngx_cpymem(b->last, "Auth-SMTP-Helo: ",
> sizeof("Auth-SMTP-Helo: ") - 1);
> - b->last = ngx_copy(b->last, s->smtp_helo.data, s->smtp_helo.len);
> + if (s->smtp_helo.len) {
> + b->last = ngx_copy(b->last, s->smtp_helo.data, s->smtp_helo.len);
> + }
> *b->last++ = CR; *b->last++ = LF;
>
> b->last = ngx_cpymem(b->last, "Auth-SMTP-From: ",
If at all, these probably should check for login.len, passwd.len,
and s->smtp_helo.len, similarly to other cases nearby, and avoid
sending the headers altogether.
> diff --git a/src/stream/ngx_stream_script.c b/src/stream/ngx_stream_script.c
> --- a/src/stream/ngx_stream_script.c
> +++ b/src/stream/ngx_stream_script.c
> @@ -842,7 +842,9 @@ ngx_stream_script_copy_var_code(ngx_stre
>
> if (value && !value->not_found) {
> p = e->pos;
> - e->pos = ngx_copy(p, value->data, value->len);
> + if (value->len) {
> + e->pos = ngx_copy(p, value->data, value->len);
> + }
>
> ngx_log_debug2(NGX_LOG_DEBUG_STREAM,
> e->session->connection->log, 0,
Obviously enough, there should be a corresponding change in
ngx_http_script_copy_var_code().
Overall, similarly to the other patch in the series, I'm highly
sceptical about doing such scattered changes based on the UB
sanitizer reports from some test runs. Rather, we should use UB
sanitizer reports to identify problematic patterns, and fix these
patterns all other the code (if at all).
Further, in this particular case I tend to think that the problem
is not with nginx code, but rather with the memcpy() interface UB
sanitizer tries to enforce. It should be completely safe to call
memcpy(p, NULL, 0), and if it doesn't, we might consider adding
appropriate guards at interface level, such as in ngx_memcpy() /
ngx_cpymem() wrappers, and not in each call. Trying to check
length everywhere is just ugly and unreadable.
Also, while recent versions of gcc are known to miscompile some
code which uses memcpy(p, NULL, 0) (see [1], with "-O2" or with
"-O1 -ftree-vrp" optimization flags in my tests), I don't think
this affects nginx code. If it does, we might also consider
force-switching off relevant optimizations (if enabled, as we use
"-O1" by default).
[1] https://stackoverflow.com/questions/5243012/is-it-guaranteed-to-be-safe-to-perform-memcpy0-0-0
--
Maxim Dounin
http://mdounin.ru/
More information about the nginx-devel
mailing list