NJS Body File Access Race Condition?

L
  • 11 Feb '23
NJS seems to be throwing an error when trying to access the temp body file
created for POST bodies that are larger than the client body buffer size.

I've verified that Nginx and NJS have access to this folder location.  As a
test, I placed a permanent file in the body temp folder location and can
read from that file without an issue in NJS code.  But if I grab the full
path to the temp body file from Nginx and then try to read that file's
contents, NJS throws an error indicating that the file doesn't exist.

Here's a code sample:
let body = '';
if(!r.variables.request_body_file){

body = r.requestText;

}
else{

log.write(logpath, r.variables.request_body_file, r);
let fs = require('fs');
body = fs.readFileSync(r.variables.request_body_file);

}

The log.write call is a utility function that just allows me to log the
contents of the variable in the sample.  In this code,
r.variables.request_body_file logs a full file path to the temp file for
the POST body.  Something like this:
/var/lib/nginx/body/0000000128

But trying to access and read that file immediately following that
yields an error:
2023/02/10 20:14:05 [error] 609307#609307: *1555 js exception: Error: No
such file or directory
    at fs.readFileSync (native)

Again, it's not a permissions issue as a permanent test file exists in the
same folder and is readable in the same code block without an issue.

This code is currently inside of a js_content filter.  Since js_content
runs very late in the process, the temp file seems like it "should" still
be in place.  After all, reading r.requestText works just fine.  The only
time the problem surfaces is when the client body exceeds the buffer size
(even if the buffer size is high) as that's where file access has to
occur.  Since the POST bodies can sometimes be very large in this
particular route, it's not really a viable option to try to match the
client body buffer size to the client body max size.  So being able to
access and read the POST body temp file is important since there are bound
to be situations where a request crosses beyond the buffer size regardless
of how high it goes (not that higher is necessarily always a good idea).

This sort of NJS behavior "seems" like some sort of race condition where
NJS is trying to access the file after Nginx has already disposed of it.
Since this is a js_content directive, it should be blocking and it seems to
be one of the few stages where access to the POST body is even possible.
So it's unclear why the client body buffer file wouldn't exist at the time
that this code runs.

Is this a bug in NJS?  Or is there some sort of alternative solution (e.g.
settings adjustment in Nginx itself) that might resolve the problem?

Thanks in advance for any insights.

-- 
Lance Dockins
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx/attachments/20230210/0562c4c8/attachment.htm>
L
  • 11 Feb '23
If it matters, I’m using Nginx 1.23.3 and NJS 0.7.10

--
Lance Dockins

On Feb 10, 2023 at 8:24 PM -0600, Lance Dockins <lance at wordkeeper.com>, wrote:
> NJS seems to be throwing an error when trying to access the temp body file created for POST bodies that are larger than the client body buffer size.
>
> I've verified that Nginx and NJS have access to this folder location.  As a test, I placed a permanent file in the body temp folder location and can read from that file without an issue in NJS code.  But if I grab the full path to the temp body file from Nginx and then try to read that file's contents, NJS throws an error indicating that the file doesn't exist.
>
> Here's a code sample:
> let body = '';
> if(!r.variables.request_body_file){
> > quote_type
> > body = r.requestText;
> }
> else{
> > quote_type
> > log.write(logpath, r.variables.request_body_file, r);
> > let fs = require('fs');
> > body = fs.readFileSync(r.variables.request_body_file);
> }
>
> The log.write call is a utility function that just allows me to log the contents of the variable in the sample.  In this code, r.variables.request_body_file logs a full file path to the temp file for the POST body.  Something like this:
> /var/lib/nginx/body/0000000128
>
> But trying to access and read that file immediately following that yields an error:
> 2023/02/10 20:14:05 [error] 609307#609307: *1555 js exception: Error: No such file or directory
>     at fs.readFileSync (native)
>
> Again, it's not a permissions issue as a permanent test file exists in the same folder and is readable in the same code block without an issue.
>
> This code is currently inside of a js_content filter.  Since js_content runs very late in the process, the temp file seems like it "should" still be in place.  After all, reading r.requestText works just fine.  The only time the problem surfaces is when the client body exceeds the buffer size (even if the buffer size is high) as that's where file access has to occur.  Since the POST bodies can sometimes be very large in this particular route, it's not really a viable option to try to match the client body buffer size to the client body max size.  So being able to access and read the POST body temp file is important since there are bound to be situations where a request crosses beyond the buffer size regardless of how high it goes (not that higher is necessarily always a good idea).
>
> This sort of NJS behavior "seems" like some sort of race condition where NJS is trying to access the file after Nginx has already disposed of it.  Since this is a js_content directive, it should be blocking and it seems to be one of the few stages where access to the POST body is even possible.  So it's unclear why the client body buffer file wouldn't exist at the time that this code runs.
>
> Is this a bug in NJS?  Or is there some sort of alternative solution (e.g. settings adjustment in Nginx itself) that might resolve the problem?
>
> Thanks in advance for any insights.
>
>
> --
> Lance Dockins
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx/attachments/20230210/cc4e3629/attachment.htm>
D
  • 11 Feb '23
Hi Lance,

On 10.02.2023 18:24, Lance Dockins wrote:
> This sort of NJS behavior "seems" like some sort of race condition 
> where NJS is trying to access the file after Nginx has already 
> disposed of it.  Since this is a js_content directive, it should be 
> blocking and it seems to be one of the few stages where access to the 
> POST body is even possible.  So it's unclear why the client body 
> buffer file wouldn't exist at the time that this code runs.
>

Take a look at 
http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only.
L
  • 11 Feb '23
Thanks, Dmitry.

Is it correct to assume that this setting just forces all POST bodies into a file (like shutting off client body buffers)?  And would that be true even with the “clean” directive?

The “clean” value sounds like it just retains the POST body file until the request completes (at which point it’s cleaned), but if that’s an incorrect understanding, please let me know.

I was sort of hoping to use memory buffers for smaller request body sizes and only use file for larger request bodies.  But if this setting is the only solution to the problem (or if one of the settings is going to do what I’m describing), I’ll go with that.

--
Lance Dockins

On Feb 10, 2023 at 8:49 PM -0600, Dmitry Volyntsev <xeioex at nginx.com>, wrote:
> Hi Lance,
>
> On 10.02.2023 18:24, Lance Dockins wrote:
> > This sort of NJS behavior "seems" like some sort of race condition
> > where NJS is trying to access the file after Nginx has already
> > disposed of it.  Since this is a js_content directive, it should be
> > blocking and it seems to be one of the few stages where access to the
> > POST body is even possible.  So it's unclear why the client body
> > buffer file wouldn't exist at the time that this code runs.
> >
>
> Take a look at
> http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only.
>
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.nginx.org/pipermail/nginx/attachments/20230210/2fbae1b6/attachment-0001.htm>
D
  • 11 Feb '23
Hi Lance,

On 2/10/23 7:10 PM, Lance Dockins wrote:
> Thanks, Dmitry.
> 
> Is it correct to assume that this setting just forces all POST bodies 
> into a file (like shutting off client body buffers)?  And would that be 
> true even with the “clean” directive?
 >
 > The “clean” value sounds like it just retains the POST body file until
 > the request completes (at which point it’s cleaned), but if that’s an
 > incorrect understanding, please let me know.

yes, "client_body_in_file_only clean" ensures two things:

1) the request body is always in a file
2) the temporary file is unlinked from a directory at the moment the 
request finishes.

By default, client_body_in_file_only is off which means
that the file is unlinked from directory right away at the moment
the file is created (thus making the file inaccessible by a name, only
    through open file descriptor).

> I was sort of hoping to use memory buffers for smaller request body 
> sizes and only use file for larger request bodies.  But if this setting 
> is the only solution to the problem (or if one of the settings is going 
> to do what I’m describing), I’ll go with that.

The another option is to increase the  client_body_buffer_size ensuring the
the body is always in memory.

> 
> 
> --
> Lance Dockins
> 
> On Feb 10, 2023 at 8:49 PM -0600, Dmitry Volyntsev <xeioex at nginx.com>, 
> wrote:
>> Hi Lance,
>>
>> On 10.02.2023 18:24, Lance Dockins wrote:
>>> This sort of NJS behavior "seems" like some sort of race condition
>>> where NJS is trying to access the file after Nginx has already
>>> disposed of it.  Since this is a js_content directive, it should be
>>> blocking and it seems to be one of the few stages where access to the
>>> POST body is even possible.  So it's unclear why the client body
>>> buffer file wouldn't exist at the time that this code runs.
>>>
>>
>> Take a look at
>> http://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only.
>>
>>
>>
>>