Skip to content

ngx_mruby v2.7.4

Choose a tag to compare

@matsumotory matsumotory released this 01 Nov 16:04
e2aeb5d

ngx_mruby 2.7.4 is a maintenance release of the 2.x line. It changes two
behaviors of the stream module that an existing configuration or Ruby script
can observe; read "Behavior changes: read before upgrading" in the notes
before you upgrade. No directive was added, renamed or removed: rebuild the
module the way you built 2.7.3 and replace the binary or the dynamic module.

Security

This release fixes a security issue of the stream module, with its advisory:

  • Moderate — On a stream listen ... proxy_protocol, a session whose
    code waits in Nginx::Stream::Async.sleep now reads nothing from the
    client until the code resumes, and Nginx::Stream::Connection.proxy_protocol_addr
    returns nil for a connection without a PROXY source address (CWE-476, CWE-345).
    (GHSA-xjh6-6vgm-56wj)

See entries 1 and 2 of the notes for what changes and what to do (nothing,
for most configurations).

Behavior changes: read before upgrading

1. Nginx::Stream::Connection.proxy_protocol_addr and proxy_protocol_ip return nil for a connection without a PROXY source address

  • Before: with nginx 1.17.6 and later, Nginx::Stream::Connection.proxy_protocol_addr
    and Nginx::Stream::Connection.proxy_protocol_ip, as class and as instance
    methods, did not return a value for a connection that had no source address
    from a PROXY protocol header: on a listen without proxy_protocol, and
    after the header PROXY UNKNOWN (version 1) or a version 2 header with the
    command LOCAL.
  • Now: the methods return nil for such a connection. After a header that
    carries addresses, they return its source address as before.
  • Why it changed:
    • The defect: the methods returned no value for such a connection, as
      "Before" describes. The advisory
      GHSA-xjh6-6vgm-56wj
      describes the defect, the affected versions and the conditions.
    • Why the old behavior could not be kept: such a connection has no address
      to return, and nil tells it apart from a connection with one. In HTTP
      code, Nginx::Var#proxy_protocol_addr returns nil in the same case.
    • What the documentation promised: the methods were not documented.
      docs/class_and_method/README.md
      describes them now.
  • Affected: mruby_stream and mruby_stream_code code that calls these
    methods on a connection without a PROXY source address.
  • What to do: nothing for code that compares the result with a String. Code
    that calls a String method on the result handles nil, for example with
    to_s.

2. While the code of a TCP stream session sleeps, nginx reads nothing from the client, and worker_shutdown_timeout ends the session

  • Before: while the code of a TCP session waited in
    Nginx::Stream::Async.sleep, the read events of the connection were
    handled as before the code ran. On a listen with proxy_protocol, bytes
    that the client sent during the sleep could end the session (status 400 in
    the access log). When worker_shutdown_timeout expired during the sleep,
    the session did not end then.
  • Now: while the code sleeps, nginx reads nothing from the client. The bytes
    that the client sends then stay for the phase after the code: proxy_pass
    sends them to the upstream. A client that closes the connection during the
    sleep is seen by the phase after the code, as on a listen without
    proxy_protocol. When worker_shutdown_timeout expires during the sleep,
    the session ends, and the code does not run again.
  • Why it changed:
    • The defect: the read events during the sleep were handled as before the
      code ran, as "Before" describes. The advisory
      GHSA-xjh6-6vgm-56wj
      describes the defect, the affected versions and the conditions.
    • Why the old behavior could not be kept: the bytes after the header
      belong to the protocol that the later phases carry. A session that does
      not end at worker_shutdown_timeout keeps the old worker process after
      the time that the directive sets.
    • What the documentation promised:
      docs/class_and_method/README.md
      said nothing about reads during the sleep. nginx's documentation of
      worker_shutdown_timeout
      says that nginx tries to close the open connections when the timeout
      expires.
  • Affected: stream servers whose mruby_stream or mruby_stream_code code
    calls Nginx::Stream::Async.sleep on a TCP listen: the clients that send
    bytes or close the connection during the sleep on a listen with
    proxy_protocol, and a worker that shuts down (a reload, an upgrade, a
    stop) with worker_shutdown_timeout while the code sleeps.
  • What to do: nothing. The phase after the code gets the bytes of the
    client, as on a session whose code does not sleep.
  • Not changed by this release: a UDP session. Each datagram of its client
    during the sleep runs the code again, as in 2.7.3, and
    worker_shutdown_timeout does not end a UDP session whose code sleeps.

ngx_mruby 2.7.4は、2.x系列のメンテナンスリリースです。既存の設定やRubyのスクリプトから分かる、streamモジュールの2つの挙動を変えます。アップグレードの前に、ノートの「アップグレードの前に読む挙動の変更」を読んでください。追加、改名、削除したディレクティブはありません。2.7.3をビルドしたときと同じ方法でモジュールをビルドし、バイナリか動的モジュールを置き換えてください。

セキュリティ

このリリースは、streamモジュールのセキュリティ問題を、アドバイザリとともに修正します。

  • Moderate — streamのlisten ... proxy_protocolで、コードがNginx::Stream::Async.sleepで待っているセッションは、コードが再開するまでクライアントから何も読まなくなりました。また、Nginx::Stream::Connection.proxy_protocol_addrは、PROXYの送信元のアドレスが無い接続でnilを返します(CWE-476、CWE-345)。(GHSA-xjh6-6vgm-56wj)

何が変わり何をすべきかは、ノートの項目1と項目2にあります。ほとんどの設定では、何もする必要はありません。

アップグレードの前に読む挙動の変更

1. Nginx::Stream::Connection.proxy_protocol_addrとproxy_protocol_ipは、PROXYの送信元のアドレスが無い接続でnilを返します

  • 変更前:nginx 1.17.6以降では、Nginx::Stream::Connection.proxy_protocol_addrとNginx::Stream::Connection.proxy_protocol_ipは、クラスメソッドでもインスタンスメソッドでも、PROXYプロトコルのヘッダーから得た送信元のアドレスが無い接続で値を返しませんでした。そのような接続は、proxy_protocolの無いlistenの接続と、バージョン1のヘッダーPROXY UNKNOWNか、コマンドがLOCALのバージョン2のヘッダーを受けた接続です。
  • 変更後:これらのメソッドは、そのような接続でnilを返します。アドレスを持つヘッダーの後では、これまでどおり送信元のアドレスを返します。
  • 変更の理由:
    • 不具合:「変更前」に書いたとおり、これらのメソッドは、そのような接続で値を返しませんでした。アドバイザリGHSA-xjh6-6vgm-56wjが、不具合、影響する版、条件を説明しています。
    • 前の挙動を残せなかった理由:そのような接続には、返すアドレスがありません。nilを返すと、アドレスのある接続と区別できます。HTTPのコードでは、Nginx::Var#proxy_protocol_addrが同じ場合にnilを返します。
    • ドキュメントが示していた挙動:これらのメソッドには、ドキュメントがありませんでした。いまはdocs/class_and_method/README.mdが説明しています。
  • 影響の範囲:PROXYの送信元のアドレスが無い接続で、これらのメソッドを呼ぶmruby_streamとmruby_stream_codeのコードです。
  • 必要な対応:結果をStringと比べるコードでは、対応は要りません。結果にStringのメソッドを呼ぶコードでは、to_sなどでnilを扱ってください。

2. TCPのstreamのセッションのコードがsleepしている間、nginxはクライアントから何も読まず、worker_shutdown_timeoutでセッションが終わります

  • 変更前:TCPのセッションのコードがNginx::Stream::Async.sleepで待っている間、接続の読み込みのイベントは、コードが動く前と同じに扱われていました。proxy_protocolを付けたlistenでは、sleepの間にクライアントが送ったバイトでセッションが終わることがあり、アクセスログのステータスは400でした。sleepの間にworker_shutdown_timeoutが過ぎても、セッションはそのときに終わりませんでした。
  • 変更後:コードがsleepしている間、nginxはクライアントから何も読みません。その間にクライアントが送ったバイトは、コードの後のフェーズのために残ります。proxy_passは、そのバイトをupstreamへ送ります。sleepの間にクライアントが接続を閉じると、proxy_protocolの無いlistenと同じく、コードの後のフェーズがそれを検出します。sleepの間にworker_shutdown_timeoutが過ぎると、セッションは終わり、コードはもう一度動きません。
  • 変更の理由:
    • 不具合:「変更前」に書いたとおり、sleepの間の読み込みのイベントが、コードが動く前と同じに扱われていました。アドバイザリGHSA-xjh6-6vgm-56wjが、不具合、影響する版、条件を説明しています。
    • 前の挙動を残せなかった理由:ヘッダーの後のバイトは、後のフェーズが運ぶプロトコルのものです。worker_shutdown_timeoutで終わらないセッションは、ディレクティブが決めた時間を過ぎても古いワーカープロセスを残します。
    • ドキュメントが示していた挙動:docs/class_and_method/README.mdは、sleepの間の読み込みについて何も書いていませんでした。nginxのworker_shutdown_timeoutのドキュメントは、タイムアウトが過ぎるとnginxが開いている接続を閉じようとすると書いています。
  • 影響の範囲:TCPのlistenで、mruby_streamかmruby_stream_codeのコードがNginx::Stream::Async.sleepを呼ぶstreamのサーバーです。proxy_protocolを付けたlistenでsleepの間にバイトを送るか接続を閉じるクライアントと、コードがsleepしている間にworker_shutdown_timeoutを付けて終わるワーカーが影響を受けます。ワーカーが終わるのは、リロード、アップグレード、停止のときです。
  • 必要な対応:ありません。コードがsleepしないセッションと同じく、コードの後のフェーズがクライアントのバイトを受け取ります。
  • このリリースで変わらない挙動:UDPのセッションです。sleepの間にクライアントから届くデータグラムは、2.7.3と同じく、1つごとにコードをもう一度動かします。worker_shutdown_timeoutは、コードがsleepしているUDPのセッションを終わらせません。

Fixes

None apart from the two entries above.

Build and test changes

  • CI: a ready pull request that changes the code or the tests runs four
    cells of the build matrix; every cell runs in the nightly run under
    valgrind, on the push of a tag and on a pull request with the label
    full-matrix (#704).
  • Tests: test/t/cases/stream_proxy_protocol_wait.rb (port 12384) and the
    mode stream_master of test/t/cases/_second_instance.rb (ports 12385 to
    12388).

Performance

The callgrind comparison (test/perf/compare.sh) builds nginx without
--with-stream, and the changes since 2.7.3 that reach the built module are
in src/stream/ only, so 2.7.4 and 2.7.3 give the same measured binary. Per
stream session, the fix adds a comparison and two stores when the code of a
TCP session starts to wait, a comparison and a store when it resumes, and a
NULL check in proxy_protocol_addr.

Upgrading

Supported nginx versions: the nginx mainline and stable lines; CI builds nginx
1.31.8, 1.30.6, 1.28.3 and 1.26.3. See "Upgrading" in the notes for the
per-entry "What to do".

Full release notes

The complete, per-entry notes are in the repository:
docs/releases/v2.7.4.md
(English) and
docs/releases/v2.7.4.ja.md
(Japanese).