| 1258 | } |
| 1259 | |
| 1260 | func TestSessionNoLeakAfterExit_worker(t *testing.T) { |
| 1261 | runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) { |
| 1262 | // Client A: Set a secret value in session and call exit(1) |
| 1263 | clientA := &http.Client{} |
| 1264 | resp1, err := clientA.Get(ts.URL + "/session-leak.php?action=set_and_exit&value=exit_secret&client_id=exitClient") |
| 1265 | assert.NoError(t, err) |
| 1266 | body1, _ := io.ReadAll(resp1.Body) |
| 1267 | _ = resp1.Body.Close() |
| 1268 | |
| 1269 | body1Str := string(body1) |
| 1270 | t.Logf("Client A set and exit: %s", body1Str) |
| 1271 | // The response may be incomplete due to exit(1) |
| 1272 | assert.Contains(t, body1Str, "BEFORE_EXIT") |
| 1273 | |
| 1274 | // Client B: Check that session is empty (should not see Client A's data) |
| 1275 | // Retry until the worker has restarted after exit(1) |
| 1276 | clientB := &http.Client{} |
| 1277 | var body2Str string |
| 1278 | assert.Eventually(t, func() bool { |
| 1279 | resp2, err := clientB.Get(ts.URL + "/session-leak.php?action=check_empty") |
| 1280 | if err != nil { |
| 1281 | return false |
| 1282 | } |
| 1283 | body2, _ := io.ReadAll(resp2.Body) |
| 1284 | _ = resp2.Body.Close() |
| 1285 | body2Str = string(body2) |
| 1286 | return strings.Contains(body2Str, "SESSION_CHECK") |
| 1287 | }, 2*time.Second, 10*time.Millisecond, "Worker did not restart in time after exit(1)") |
| 1288 | |
| 1289 | t.Logf("Client B check empty after exit: %s", body2Str) |
| 1290 | assert.Contains(t, body2Str, "SESSION_EMPTY=true", |
| 1291 | "Client B should have empty session after Client A's exit(1).\nResponse: %s", body2Str) |
| 1292 | assert.NotContains(t, body2Str, "exit_secret", |
| 1293 | "Client A's secret should not leak to Client B after exit(1).\nResponse: %s", body2Str) |
| 1294 | |
| 1295 | // Client C: Try to read session (should also be empty) |
| 1296 | clientC := &http.Client{} |
| 1297 | resp3, err := clientC.Get(ts.URL + "/session-leak.php?action=get") |
| 1298 | assert.NoError(t, err) |
| 1299 | body3, _ := io.ReadAll(resp3.Body) |
| 1300 | _ = resp3.Body.Close() |
| 1301 | |
| 1302 | body3Str := string(body3) |
| 1303 | t.Logf("Client C get session after exit: %s", body3Str) |
| 1304 | assert.Contains(t, body3Str, "SESSION_READ") |
| 1305 | assert.Contains(t, body3Str, "secret=NOT_FOUND", |
| 1306 | "Client C should not find any secret after exit(1).\nResponse: %s", body3Str) |
| 1307 | |
| 1308 | }, &testOptions{ |
| 1309 | workerScript: "session-leak.php", |
| 1310 | nbWorkers: 1, |
| 1311 | nbParallelRequests: 1, |
| 1312 | realServer: true, |
| 1313 | }) |
| 1314 | } |
| 1315 | |
| 1316 | func TestOpcachePreload_module(t *testing.T) { |
| 1317 | testOpcachePreload(t, &testOptions{env: map[string]string{"TEST": "123"}, realServer: true}) |