Merge ~athos-ribeiro/ubuntu/+source/php8.1:lp2017207-leak-lunar into ubuntu/+source/php8.1:ubuntu/lunar-devel

Proposed by Athos Ribeiro
Status: Merged
Approved by: git-ubuntu bot
Approved revision: not available
Merged at revision: 6dce2b1b318e328dc4d19a3ec3498d006040cff7
Proposed branch: ~athos-ribeiro/ubuntu/+source/php8.1:lp2017207-leak-lunar
Merge into: ubuntu/+source/php8.1:ubuntu/lunar-devel
Diff against target: 247 lines (+225/-0)
3 files modified
debian/changelog (+7/-0)
debian/patches/fix-map-ptr-mem-leak.patch (+217/-0)
debian/patches/series (+1/-0)
Reviewer Review Type Date Requested Status
git-ubuntu bot Approve
Sergio Durigan Junior (community) Approve
Canonical Server Reporter Pending
Review via email: mp+444859@code.launchpad.net

Description of the change

Fix for LP: #2017207.

PPA: https://launchpad.net/~athos-ribeiro/+archive/ubuntu/php-fpm-leak/+packages

DEP8 test suite run result:

  - php8.1/8.1.12-1ubuntu4.1~ppa1
    + ✅ php8.1 on lunar for amd64 @ 15.06.23 13:15:41 Log️ 🗒️
    + ✅ php8.1 on lunar for arm64 @ 15.06.23 13:18:46 Log️ 🗒️
    + ✅ php8.1 on lunar for i386 @ 15.06.23 13:06:03 Log️ 🗒️
    + ✅ php8.1 on lunar for ppc64el @ 15.06.23 13:12:20 Log️ 🗒️
    + ✅ php8.1 on lunar for s390x @ 15.06.23 13:28:35 Log️ 🗒️
    + ✅ php8.1 on lunar for armhf @ 15.06.23 16:57:48 Log️ 🗒️

To post a comment you must log in.
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thanks, Athos.

LGTM, +1.

review: Approve
Revision history for this message
git-ubuntu bot (git-ubuntu-bot) wrote :

Approvers: athos-ribeiro, sergiodj
Uploaders: athos-ribeiro, sergiodj
MP auto-approved

review: Approve
Revision history for this message
Athos Ribeiro (athos-ribeiro) wrote :

Thanks, Sergio. Uploaded!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 4de333d..60125fa 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,10 @@
6+php8.1 (8.1.12-1ubuntu4.1) lunar; urgency=medium
7+
8+ * d/p/fix-map-ptr-mem-leak.patch: Fix map_ptr opcache-less fpm memory leak.
9+ (LP: #2017207)
10+
11+ -- Athos Ribeiro <athos.ribeiro@canonical.com> Wed, 14 Jun 2023 16:57:52 -0300
12+
13 php8.1 (8.1.12-1ubuntu4) lunar; urgency=medium
14
15 * SECURITY UPDATE: password_verify() accepts invalid Blowfish hashes
16diff --git a/debian/patches/fix-map-ptr-mem-leak.patch b/debian/patches/fix-map-ptr-mem-leak.patch
17new file mode 100644
18index 0000000..ee41b8c
19--- /dev/null
20+++ b/debian/patches/fix-map-ptr-mem-leak.patch
21@@ -0,0 +1,217 @@
22+From ff62d117a35509699f8bac8b0750a956914da1b7 Mon Sep 17 00:00:00 2001
23+From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
24+Date: Sat, 4 Mar 2023 23:22:05 +0100
25+Subject: [PATCH] Fix GH-8646: Memory leak PHP FPM 8.1
26+
27+Fixes GH-8646
28+See https://github.com/php/php-src/issues/8646 for thorough discussion.
29+
30+Interned strings that hold class entries can get a corresponding slot in map_ptr for the CE cache.
31+map_ptr works like a bump allocator: there is a counter which increases to allocate the next slot in the map.
32+
33+For class name strings in non-opcache we have:
34+ - on startup: permanent + interned
35+ - on request: interned
36+For class name strings in opcache we have:
37+ - on startup: permanent + interned
38+ - on request: either not interned at all, which we can ignore because they won't get a CE cache entry
39+ or they were already permanent + interned
40+ or we get a new permanent + interned string in the opcache persistence code
41+
42+Notice that the map_ptr layout always has the permanent strings first, and the request strings after.
43+In non-opcache, a request string may get a slot in map_ptr, and that interned request string
44+gets destroyed at the end of the request. The corresponding map_ptr slot can thereafter never be used again.
45+This causes map_ptr to keep reallocating to larger and larger sizes.
46+
47+We solve it as follows:
48+We can check whether we had any interned request strings, which only happens in non-opcache.
49+If we have any, we reset map_ptr to the last permanent string.
50+We can't lose any permanent strings because of map_ptr's layout.
51+
52+Closes GH-10783.
53+
54+Bug: https://github.com/php/php-src/issues/8646
55+Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/php8.1/+bug/2017207
56+Origin: upstream, https://github.com/php/php-src/commit/ff62d117a35509699f8bac8b0750a956914da1b7
57+Last-Update: 2023-06-14
58+---
59+ NEWS | 1 +
60+ Zend/zend.c | 28 +++++++++++++++++++
61+ ext/zend_test/test.c | 6 +++++
62+ ext/zend_test/test.stub.php | 2 ++
63+ ext/zend_test/test_arginfo.h | 10 ++++---
64+ sapi/fpm/tests/gh8646.phpt | 52 ++++++++++++++++++++++++++++++++++++
65+ 6 files changed, 96 insertions(+), 3 deletions(-)
66+ create mode 100644 sapi/fpm/tests/gh8646.phpt
67+
68+diff --git a/Zend/zend.c b/Zend/zend.c
69+index bd2a04e2ba9d..b14b2c701a5b 100644
70+--- a/Zend/zend.c
71++++ b/Zend/zend.c
72+@@ -1286,6 +1286,34 @@ ZEND_API void zend_deactivate(void) /* {{{ */
73+
74+ zend_destroy_rsrc_list(&EG(regular_list));
75+
76++ /* See GH-8646: https://github.com/php/php-src/issues/8646
77++ *
78++ * Interned strings that hold class entries can get a corresponding slot in map_ptr for the CE cache.
79++ * map_ptr works like a bump allocator: there is a counter which increases to allocate the next slot in the map.
80++ *
81++ * For class name strings in non-opcache we have:
82++ * - on startup: permanent + interned
83++ * - on request: interned
84++ * For class name strings in opcache we have:
85++ * - on startup: permanent + interned
86++ * - on request: either not interned at all, which we can ignore because they won't get a CE cache entry
87++ * or they were already permanent + interned
88++ * or we get a new permanent + interned string in the opcache persistence code
89++ *
90++ * Notice that the map_ptr layout always has the permanent strings first, and the request strings after.
91++ * In non-opcache, a request string may get a slot in map_ptr, and that interned request string
92++ * gets destroyed at the end of the request. The corresponding map_ptr slot can thereafter never be used again.
93++ * This causes map_ptr to keep reallocating to larger and larger sizes.
94++ *
95++ * We solve it as follows:
96++ * We can check whether we had any interned request strings, which only happens in non-opcache.
97++ * If we have any, we reset map_ptr to the last permanent string.
98++ * We can't lose any permanent strings because of map_ptr's layout.
99++ */
100++ if (zend_hash_num_elements(&CG(interned_strings)) > 0) {
101++ zend_map_ptr_reset();
102++ }
103++
104+ #if GC_BENCH
105+ fprintf(stderr, "GC Statistics\n");
106+ fprintf(stderr, "-------------\n");
107+diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
108+index 6c5bc119ab31..ecfef09c6f7e 100644
109+--- a/ext/zend_test/test.c
110++++ b/ext/zend_test/test.c
111+@@ -321,6 +321,12 @@ static ZEND_FUNCTION(zend_test_parameter_with_attribute)
112+ RETURN_LONG(1);
113+ }
114+
115++static ZEND_FUNCTION(zend_get_map_ptr_last)
116++{
117++ ZEND_PARSE_PARAMETERS_NONE();
118++ RETURN_LONG(CG(map_ptr_last));
119++}
120++
121+ static zend_object *zend_test_class_new(zend_class_entry *class_type)
122+ {
123+ zend_object *obj = zend_objects_new(class_type);
124+diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
125+index 9a49d31fa97d..bc6a9d54e3bf 100644
126+--- a/ext/zend_test/test.stub.php
127++++ b/ext/zend_test/test.stub.php
128+@@ -115,6 +115,8 @@ function zend_test_parameter_with_attribute(string $parameter): int {}
129+ function zend_get_current_func_name(): string {}
130+
131+ function zend_call_method(string $class, string $method, mixed $arg1 = UNKNOWN, mixed $arg2 = UNKNOWN): mixed {}
132++
133++ function zend_get_map_ptr_last(): int {}
134+ }
135+
136+ namespace ZendTestNS {
137+diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
138+index ebc4076daca8..5a7cc67b06d3 100644
139+--- a/ext/zend_test/test_arginfo.h
140++++ b/ext/zend_test/test_arginfo.h
141+@@ -1,5 +1,5 @@
142+ /* This is a generated file, edit the .stub.php file instead.
143+- * Stub hash: 27df6a7b48574b5c6c9a54c618fce300c7a8bd13 */
144++ * Stub hash: 1eca5b01969498e67501a59dc69ba4c01263c4d9 */
145+
146+ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
147+ ZEND_END_ARG_INFO()
148+@@ -79,12 +79,14 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_call_method, 0, 2, IS_MIXED
149+ ZEND_ARG_TYPE_INFO(0, arg2, IS_MIXED, 0)
150+ ZEND_END_ARG_INFO()
151+
152+-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
153++ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_map_ptr_last, 0, 0, IS_LONG, 0)
154+ ZEND_END_ARG_INFO()
155+
156+-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass_is_object, 0, 0, IS_LONG, 0)
157++ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
158+ ZEND_END_ARG_INFO()
159+
160++#define arginfo_class__ZendTestClass_is_object arginfo_zend_get_map_ptr_last
161++
162+ ZEND_BEGIN_ARG_INFO_EX(arginfo_class__ZendTestClass___toString, 0, 0, 0)
163+ ZEND_END_ARG_INFO()
164+
165+@@ -136,6 +138,7 @@ static ZEND_FUNCTION(zend_get_unit_enum);
166+ static ZEND_FUNCTION(zend_test_parameter_with_attribute);
167+ static ZEND_FUNCTION(zend_get_current_func_name);
168+ static ZEND_FUNCTION(zend_call_method);
169++static ZEND_FUNCTION(zend_get_map_ptr_last);
170+ static ZEND_FUNCTION(namespaced_func);
171+ static ZEND_METHOD(_ZendTestClass, is_object);
172+ static ZEND_METHOD(_ZendTestClass, __toString);
173+@@ -173,6 +176,7 @@ static const zend_function_entry ext_functions[] = {
174+ ZEND_FE(zend_test_parameter_with_attribute, arginfo_zend_test_parameter_with_attribute)
175+ ZEND_FE(zend_get_current_func_name, arginfo_zend_get_current_func_name)
176+ ZEND_FE(zend_call_method, arginfo_zend_call_method)
177++ ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last)
178+ ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func)
179+ ZEND_FE_END
180+ };
181+diff --git a/sapi/fpm/tests/gh8646.phpt b/sapi/fpm/tests/gh8646.phpt
182+new file mode 100644
183+index 000000000000..c35b57ec916c
184+--- /dev/null
185++++ b/sapi/fpm/tests/gh8646.phpt
186+@@ -0,0 +1,52 @@
187++--TEST--
188++GH-8646 (Memory leak PHP FPM 8.1)
189++--EXTENSIONS--
190++zend_test
191++--SKIPIF--
192++<?php include "skipif.inc"; ?>
193++--FILE--
194++<?php
195++
196++require_once "tester.inc";
197++
198++$cfg = <<<EOT
199++[global]
200++error_log = {{FILE:LOG}}
201++[unconfined]
202++listen = {{ADDR}}
203++pm = dynamic
204++pm.max_children = 5
205++pm.start_servers = 1
206++pm.min_spare_servers = 1
207++pm.max_spare_servers = 3
208++EOT;
209++
210++$code = <<<EOT
211++<?php
212++class MyClass {}
213++echo zend_get_map_ptr_last();
214++EOT;
215++
216++$tester = new FPM\Tester($cfg, $code);
217++$tester->start();
218++$tester->expectLogStartNotices();
219++$map_ptr_last_values = [];
220++for ($i = 0; $i < 10; $i++) {
221++ $map_ptr_last_values[] = (int) $tester->request()->getBody();
222++}
223++// Ensure that map_ptr_last did not increase
224++var_dump(count(array_unique($map_ptr_last_values, SORT_REGULAR)) === 1);
225++$tester->terminate();
226++$tester->expectLogTerminatingNotices();
227++$tester->close();
228++
229++?>
230++Done
231++--EXPECT--
232++bool(true)
233++Done
234++--CLEAN--
235++<?php
236++require_once "tester.inc";
237++FPM\Tester::clean();
238++?>
239diff --git a/debian/patches/series b/debian/patches/series
240index 82e11c4..7b18a88 100644
241--- a/debian/patches/series
242+++ b/debian/patches/series
243@@ -51,3 +51,4 @@ CVE-2023-0567-2.patch
244 CVE-2023-0568.patch
245 CVE-2023-0662-1.patch
246 CVE-2023-0662-2.patch
247+fix-map-ptr-mem-leak.patch

Subscribers

People subscribed via source and target branches