Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nginx: fix failure to serve on aliases with regex captures #66532

Closed
wants to merge 3 commits into from

Conversation

yorickvP
Copy link
Contributor

Motivation for this change

It turns out the previous patch did not take in consideration that you could do alias a/$1/b. This means realpath would return NULL on this and nginx would return a 500 error instead.

Things done

This PR does the following:

  • add a test for this case
  • use the actual file path instead of the configured root dir, so symlinks to outside of the nix store work as expected
  • do not throw out the whole http request if realpath returns NULL
  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nix-review --run "nix-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Determined the impact on package closure size (by running nix path-info -S before and after)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.
Notify maintainers

cc @edef1c @yegortimoshenko @aszlig @grahamc @domenkozar

@mmahut
Copy link
Member

mmahut commented Aug 12, 2019

@GrahamcOfBorg test nginx

Copy link
Member

@7c6f434c 7c6f434c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But maybe use git mv on the patch — the patch gets complicated and it is hard to see the changes to the patch…

Copy link
Member

@lukateras lukateras left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be able to review after git mv when there is a line-by-line diff.

@yorickvP
Copy link
Contributor Author

yorickvP commented Aug 13, 2019 via email

@lukateras
Copy link
Member

It seems that my choice of file name and format was unfortunate, maybe this could be better off as a diff (instead of a patch) and without Nginx version in the file name?

ngx_int_t ngx_http_set_content_type(ngx_http_request_t *r);
void ngx_http_set_exten(ngx_http_request_t *r);
-ngx_int_t ngx_http_set_etag(ngx_http_request_t *r);
+ngx_int_t ngx_http_set_etag(ngx_http_request_t *r, ngx_str_t *path);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit unfortunate here, because this changes the API for third-party extension modules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern, I don't think we should ever change API of any programs that we patch (i.e. no headers changes).

+
+ if (etag->value.data == NULL) {
+ etag->hash = 0;
+ return NGX_ERROR;
Copy link
Member

@aszlig aszlig Aug 13, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memleak: ngx_free(real);

Addendum: The reason why this could memleak is when real is not NULL but it's not pointing to the Nix store.

+ - etag->value.data;
+ }

r->headers_out.etag = etag;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: ngx_free(real);

+ }
+
+ ngx_memcpy(etag->value.data, ptr1, etag->value.len);
+ ngx_free(real);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed if you just move it past the else branch below.

Copy link
Member

@aszlig aszlig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned by @yegortimoshenko, it would probably make sense to just use a plain diff and either keep the versioned name (it still applies on version 1.15.4) or drop it entirely.

Apart from the mentioned memleaks, the API change is something I'd try to avoid as we don't know which external modules people might use.

@yorickvP
Copy link
Contributor Author

updated: renamed the patch file in separate commits. Fixed API compatibility. Fixed memleaks.
@aszlig @yegortimoshenko
261f85d is the relevant diff.

@ofborg ofborg bot requested a review from 7c6f434c August 14, 2019 11:20
@7c6f434c
Copy link
Member

I wonder if it makes sense to allow NULL path and provide the old external API as a wrapper.

@yorickvP
Copy link
Contributor Author

@7c6f434c that's what I'm doing now, at least

+
+ if (etag->value.data == NULL) {
+ etag->hash = 0;
+ if (real != NULL) ngx_free(real);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other places you don't do a NULL check (and apparently at least glibc version of free is OK with free(NULL)) — why is this place different?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do a NULL check everywhere it could be null, which isn't in the other branch of the if

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngx_free is just an alias for free of the corresponding C library, from http://www.open-std.org/JTC1/SC22/wg14/www/docs/n1124.pdf section 7.20.3.2:

If ptr is a null pointer, no action occurs.

So we don't need to do explicit checks like this.


etag = ngx_list_push(&r->headers_out.headers);
if (etag == NULL) {
+ ngx_free(real);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you do NULL checks before free, this seems reachable without setting real when path is NULL

@domenkozar
Copy link
Member

@yorickvP do you plan to address feedback?

@cransom
Copy link
Contributor

cransom commented Dec 4, 2019

I'd like to see this get more attention/merged. Without it, my nginx config was unable to serve user directories (eg example.com/~user/foo) which caused some good head scratching for a while until I found the PR.

@yorickvP
Copy link
Contributor Author

yorickvP commented Dec 4, 2019 via email

@jglukasik
Copy link
Contributor

+1 on this getting more attention, this breaks my dynamic subdomain setup. I've been able to workaround this by overriding the patches attribute in the nginx package used by the service. Sharing my relevant configuration.nix lines below in case anyone else is having similar troubles:

services = {
  nginx = {
    enable = true;

    # don't apply patches, they break my subdomain setup
    # https://github.com/NixOS/nixpkgs/pull/66532
    package = (pkgs.nginx.overrideAttrs (oldAttrs: {
      name = "nginx-temp-no-patches";
      patches = [];
    }));

    virtualHosts = {
      "subs.ex.example.com" = {
        serverName = "~^(?<sub>.+)\\.ex\\.example\\.com$";
        root = "/var/www/ex.example.com/$sub";
      };
    };
  };
};

@aszlig
Copy link
Member

aszlig commented Feb 21, 2020

@jglukasik: See also #80671.

dtzWill pushed a commit to dtzWill/nixpkgs that referenced this pull request Feb 25, 2020
While our ETag patch works pretty fine if it comes to serving data off
store paths, it unfortunately broke something that might be a bit more
common, namely when using regexes to extract path components of
location directives for example.

Recently, @devhell has reported a bug with a nginx location directive
like this:

  location ~^/\~([a-z0-9_]+)(/.*)?$" {
    alias /home/$1/public_html$2;
  }

While this might look harmless at first glance, it does however cause
issues with our ETag patch. The alias directive gets broken up by nginx
like this:

  *2 http script copy: "/home/"
  *2 http script capture: "foo"
  *2 http script copy: "/public_html/"
  *2 http script capture: "bar.txt"

In our patch however, we use realpath(3) to get the canonicalised path
from ngx_http_core_loc_conf_s.root, which returns the *configured* value
from the root or alias directive. So in the example above, realpath(3)
boils down to the following syscalls:

  lstat("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
  lstat("/home/$1", 0x7ffd08da6f60) = -1 ENOENT (No such file or directory)

During my review[1] of the initial patch, I didn't actually notice that
what we're doing here is returning NGX_ERROR if the realpath(3) call
fails, which in turn causes an HTTP 500 error.

Since our patch actually made the canonicalisation (and thus additional
syscalls) necessary, we really shouldn't introduce an additional error
so let's - at least for now - silently skip return value if realpath(3)
has failed.

However since we're using the unaltered root from the config we have
another issue, consider this root:

  /nix/store/...-abcde/$1

Calling realpath(3) on this path will fail (except if there's a file
called "$1" of course), so even this fix is not enough because it
results in the ETag not being set to the store path hash.

While this is very ugly and we should fix this very soon, it's not as
serious as getting HTTP 500 errors for serving static files.

I added a small NixOS VM test, which uses the example above as a
regression test.

It seems that my memory is failing these days, since apparently I *knew*
about this issue since digging for existing issues in nixpkgs, I found
this similar pull request which I even reviewed:

NixOS#66532

However, since the comments weren't addressed and the author hasn't
responded to the pull request, I decided to keep this very commit and do
a follow-up pull request.

[1]: NixOS#48337

Signed-off-by: aszlig <aszlig@nix.build>
Reported-by: @devhell
(cherry picked from commit f6c7635)
@aszlig aszlig closed this in e1d63ad Mar 28, 2020
aszlig added a commit that referenced this pull request Mar 28, 2020
While our ETag patch works pretty fine if it comes to serving data off
store paths, it unfortunately broke something that might be a bit more
common, namely when using regexes to extract path components of
location directives for example.

Recently, @devhell has reported a bug with a nginx location directive
like this:

  location ~^/\~([a-z0-9_]+)(/.*)?$" {
    alias /home/$1/public_html$2;
  }

While this might look harmless at first glance, it does however cause
issues with our ETag patch. The alias directive gets broken up by nginx
like this:

  *2 http script copy: "/home/"
  *2 http script capture: "foo"
  *2 http script copy: "/public_html/"
  *2 http script capture: "bar.txt"

In our patch however, we use realpath(3) to get the canonicalised path
from ngx_http_core_loc_conf_s.root, which returns the *configured* value
from the root or alias directive. So in the example above, realpath(3)
boils down to the following syscalls:

  lstat("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
  lstat("/home/$1", 0x7ffd08da6f60) = -1 ENOENT (No such file or directory)

During my review[1] of the initial patch, I didn't actually notice that
what we're doing here is returning NGX_ERROR if the realpath(3) call
fails, which in turn causes an HTTP 500 error.

Since our patch actually made the canonicalisation (and thus additional
syscalls) necessary, we really shouldn't introduce an additional error
so let's - at least for now - silently skip return value if realpath(3)
has failed.

However since we're using the unaltered root from the config we have
another issue, consider this root:

  /nix/store/...-abcde/$1

Calling realpath(3) on this path will fail (except if there's a file
called "$1" of course), so even this fix is not enough because it
results in the ETag not being set to the store path hash.

While this is very ugly and we should fix this very soon, it's not as
serious as getting HTTP 500 errors for serving static files.

I added a small NixOS VM test, which uses the example above as a
regression test.

It seems that my memory is failing these days, since apparently I *knew*
about this issue since digging for existing issues in nixpkgs, I found
this similar pull request which I even reviewed:

#66532

However, since the comments weren't addressed and the author hasn't
responded to the pull request, I decided to keep this very commit and do
a follow-up pull request.

[1]: #48337

Signed-off-by: aszlig <aszlig@nix.build>
Reported-by: @devhell
Acked-by: @7c6f434c
Acked-by: @yorickvP
Merges: #80671
Fixes: #66532
(cherry picked from commit e1d63ad)
aszlig added a commit that referenced this pull request Mar 28, 2020
While our ETag patch works pretty fine if it comes to serving data off
store paths, it unfortunately broke something that might be a bit more
common, namely when using regexes to extract path components of
location directives for example.

Recently, @devhell has reported a bug with a nginx location directive
like this:

  location ~^/\~([a-z0-9_]+)(/.*)?$" {
    alias /home/$1/public_html$2;
  }

While this might look harmless at first glance, it does however cause
issues with our ETag patch. The alias directive gets broken up by nginx
like this:

  *2 http script copy: "/home/"
  *2 http script capture: "foo"
  *2 http script copy: "/public_html/"
  *2 http script capture: "bar.txt"

In our patch however, we use realpath(3) to get the canonicalised path
from ngx_http_core_loc_conf_s.root, which returns the *configured* value
from the root or alias directive. So in the example above, realpath(3)
boils down to the following syscalls:

  lstat("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
  lstat("/home/$1", 0x7ffd08da6f60) = -1 ENOENT (No such file or directory)

During my review[1] of the initial patch, I didn't actually notice that
what we're doing here is returning NGX_ERROR if the realpath(3) call
fails, which in turn causes an HTTP 500 error.

Since our patch actually made the canonicalisation (and thus additional
syscalls) necessary, we really shouldn't introduce an additional error
so let's - at least for now - silently skip return value if realpath(3)
has failed.

However since we're using the unaltered root from the config we have
another issue, consider this root:

  /nix/store/...-abcde/$1

Calling realpath(3) on this path will fail (except if there's a file
called "$1" of course), so even this fix is not enough because it
results in the ETag not being set to the store path hash.

While this is very ugly and we should fix this very soon, it's not as
serious as getting HTTP 500 errors for serving static files.

I added a small NixOS VM test, which uses the example above as a
regression test.

It seems that my memory is failing these days, since apparently I *knew*
about this issue since digging for existing issues in nixpkgs, I found
this similar pull request which I even reviewed:

#66532

However, since the comments weren't addressed and the author hasn't
responded to the pull request, I decided to keep this very commit and do
a follow-up pull request.

[1]: #48337

Signed-off-by: aszlig <aszlig@nix.build>
Reported-by: @devhell
Acked-by: @7c6f434c
Acked-by: @yorickvP
Merges: #80671
Fixes: #66532
(cherry picked from commit e1d63ad)
stigok pushed a commit to stigok/nixpkgs that referenced this pull request Jun 12, 2020
While our ETag patch works pretty fine if it comes to serving data off
store paths, it unfortunately broke something that might be a bit more
common, namely when using regexes to extract path components of
location directives for example.

Recently, @devhell has reported a bug with a nginx location directive
like this:

  location ~^/\~([a-z0-9_]+)(/.*)?$" {
    alias /home/$1/public_html$2;
  }

While this might look harmless at first glance, it does however cause
issues with our ETag patch. The alias directive gets broken up by nginx
like this:

  *2 http script copy: "/home/"
  *2 http script capture: "foo"
  *2 http script copy: "/public_html/"
  *2 http script capture: "bar.txt"

In our patch however, we use realpath(3) to get the canonicalised path
from ngx_http_core_loc_conf_s.root, which returns the *configured* value
from the root or alias directive. So in the example above, realpath(3)
boils down to the following syscalls:

  lstat("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
  lstat("/home/$1", 0x7ffd08da6f60) = -1 ENOENT (No such file or directory)

During my review[1] of the initial patch, I didn't actually notice that
what we're doing here is returning NGX_ERROR if the realpath(3) call
fails, which in turn causes an HTTP 500 error.

Since our patch actually made the canonicalisation (and thus additional
syscalls) necessary, we really shouldn't introduce an additional error
so let's - at least for now - silently skip return value if realpath(3)
has failed.

However since we're using the unaltered root from the config we have
another issue, consider this root:

  /nix/store/...-abcde/$1

Calling realpath(3) on this path will fail (except if there's a file
called "$1" of course), so even this fix is not enough because it
results in the ETag not being set to the store path hash.

While this is very ugly and we should fix this very soon, it's not as
serious as getting HTTP 500 errors for serving static files.

I added a small NixOS VM test, which uses the example above as a
regression test.

It seems that my memory is failing these days, since apparently I *knew*
about this issue since digging for existing issues in nixpkgs, I found
this similar pull request which I even reviewed:

NixOS#66532

However, since the comments weren't addressed and the author hasn't
responded to the pull request, I decided to keep this very commit and do
a follow-up pull request.

[1]: NixOS#48337

Signed-off-by: aszlig <aszlig@nix.build>
Reported-by: @devhell
Acked-by: @7c6f434c
Acked-by: @yorickvP
Merges: NixOS#80671
Fixes: NixOS#66532
(cherry picked from commit e1d63ad)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants