Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,25 @@ Prerequisites:

##### x86_64
```
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.x86_64.rpm
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.x86_64.rpm
```

##### arm64 / aarch64
```
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.aarch64.rpm
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.aarch64.rpm
```

#### For Debian-based Systems (Debian, Ubuntu)

##### x86_64
```
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.x86_64.deb
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.x86_64.deb
dpkg -i -E ./aikido-php-firewall.x86_64.deb
```

##### arm64 / aarch64
```
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.aarch64.deb
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.aarch64.deb
dpkg -i -E ./aikido-php-firewall.aarch64.deb
```

Expand Down
2 changes: 1 addition & 1 deletion docs/aws-elastic-beanstalk.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
```
commands:
aikido-php-firewall:
command: "rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.x86_64.rpm"
command: "rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.x86_64.rpm"
ignoreErrors: true

files:
Expand Down
2 changes: 1 addition & 1 deletion docs/fly-io.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Create a script to install the Aikido PHP Firewall during deployment:
#!/usr/bin/env bash
cd /tmp

curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.x86_64.deb
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.x86_64.deb
dpkg -i -E ./aikido-php-firewall.x86_64.deb
```

Expand Down
2 changes: 1 addition & 1 deletion docs/laravel-forge.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ You can get your token from the [Aikido Security Dashboard](https://help.aikido.

Go to "Commands" and run the following by replacing the sudo password with the one that Forge displays when the server is created:
```
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.1/aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S dpkg -i -E ./aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S service php8.4-fpm restart
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.5.2/aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S dpkg -i -E ./aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S service php8.4-fpm restart
```

![Forge Commands](./forge-commands.png)
Expand Down
2 changes: 1 addition & 1 deletion lib/agent/constants/constants.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package constants

const (
Version = "1.5.1"
Version = "1.5.2"
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
PidPath = "/run/aikido-" + Version + "/aikido-agent.pid"
ConfigUpdatedAtMethod = "GET"
Expand Down
10 changes: 2 additions & 8 deletions lib/php-extension/Aikido.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@
ZEND_DECLARE_MODULE_GLOBALS(aikido)

PHP_MINIT_FUNCTION(aikido) {
// For FrankenPHP: Set sapi_name but skip rest of LoadEnvironment during MINIT
// Full environment will be loaded in RINIT when Caddyfile env vars are available
if (sapi_module.name == std::string("frankenphp")) {
AIKIDO_GLOBAL(sapi_name) = sapi_module.name;
} else {
// For other SAPIs: Load environment during MINIT as normal
LoadEnvironment();
}
LoadSystemEnvironment();

AIKIDO_GLOBAL(logger).Init();

AIKIDO_LOG_INFO("MINIT started!\n");
Expand Down
61 changes: 39 additions & 22 deletions lib/php-extension/Environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,19 @@ std::string GetLaravelEnvVariable(const std::string& env_key) {
*/

using EnvGetterFn = std::string(*)(const std::string&);
EnvGetterFn envGetters[] = {

const std::vector<EnvGetterFn> completeEnvGetters = {
&GetSystemEnvVariable,
&GetFrankenEnvVariable,
&GetPhpEnvVariable,
&GetLaravelEnvVariable
};

std::string GetEnvVariable(const std::string& env_key) {
const std::vector<EnvGetterFn> systemEnvGetters = {
&GetSystemEnvVariable,
};

std::string GetEnvVariable(const std::vector<EnvGetterFn>& envGetters, const std::string& env_key) {
for (EnvGetterFn envGetter : envGetters) {
std::string env_value = envGetter(env_key);
if (!env_value.empty()) {
Expand All @@ -169,8 +174,8 @@ std::string GetEnvVariable(const std::string& env_key) {
return "";
}

std::string GetEnvString(const std::string& env_key, const std::string default_value) {
std::string env_value = GetEnvVariable(env_key);
std::string GetEnvString(const std::vector<EnvGetterFn>& envGetters, const std::string& env_key, const std::string default_value) {
std::string env_value = GetEnvVariable(envGetters, env_key);
if (!env_value.empty()) {
return env_value;
}
Expand All @@ -185,12 +190,16 @@ bool GetBoolFromString(const std::string& env, bool default_value) {
return default_value;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's rename this function to GetEnvBoolWithAllGetters or smth like this so it's more explicit what it does. Can remove the comment from the Includes.h after that.

bool GetEnvBool(const std::string& env_key, bool default_value) {
return GetBoolFromString(GetEnvVariable(env_key), default_value);
bool GetEnvBool(const std::vector<EnvGetterFn>& envGetters, const std::string& env_key, bool default_value) {
return GetBoolFromString(GetEnvVariable(envGetters, env_key), default_value);
}

unsigned int GetEnvNumber(const std::string& env_key, unsigned int default_value) {
std::string env_value = GetEnvVariable(env_key.c_str());
bool GetEnvBoolWithAllGetters(const std::string& env_key, bool default_value) {
return GetBoolFromString(GetEnvVariable(completeEnvGetters, env_key), default_value);
}

unsigned int GetEnvNumber(const std::vector<EnvGetterFn>& envGetters, const std::string& env_key, unsigned int default_value) {
std::string env_value = GetEnvVariable(envGetters, env_key);
if (!env_value.empty()) {
try {
unsigned int number = std::stoi(env_value);
Expand All @@ -203,26 +212,34 @@ unsigned int GetEnvNumber(const std::string& env_key, unsigned int default_value
return default_value;
}

void LoadEnvironment() {
void LoadEnvironmentFromGetters(const std::vector<EnvGetterFn>& envGetters) {
auto& logLevelStr = AIKIDO_GLOBAL(log_level_str);
auto& logLevel = AIKIDO_GLOBAL(log_level);
if (GetEnvBool("AIKIDO_DEBUG", false)) {
if (GetEnvBool(envGetters, "AIKIDO_DEBUG", false)) {
logLevelStr = "DEBUG";
logLevel = AIKIDO_LOG_LEVEL_DEBUG;
} else {
logLevelStr = GetEnvString("AIKIDO_LOG_LEVEL", "WARN");
logLevelStr = GetEnvString(envGetters, "AIKIDO_LOG_LEVEL", "WARN");
logLevel = Log::ToLevel(logLevelStr);
}

AIKIDO_GLOBAL(blocking) = GetEnvBool("AIKIDO_BLOCK", false) || GetEnvBool("AIKIDO_BLOCKING", false);;
AIKIDO_GLOBAL(disable) = GetEnvBool("AIKIDO_DISABLE", false);
AIKIDO_GLOBAL(collect_api_schema) = GetEnvBool("AIKIDO_FEATURE_COLLECT_API_SCHEMA", true);
AIKIDO_GLOBAL(localhost_allowed_by_default) = GetEnvBool("AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT", true);
AIKIDO_GLOBAL(trust_proxy) = GetEnvBool("AIKIDO_TRUST_PROXY", true);
AIKIDO_GLOBAL(disk_logs) = GetEnvBool("AIKIDO_DISK_LOGS", false);
AIKIDO_GLOBAL(blocking) = GetEnvBool(envGetters, "AIKIDO_BLOCK", false) || GetEnvBool(envGetters, "AIKIDO_BLOCKING", false);
AIKIDO_GLOBAL(disable) = GetEnvBool(envGetters, "AIKIDO_DISABLE", false);
AIKIDO_GLOBAL(collect_api_schema) = GetEnvBool(envGetters,"AIKIDO_FEATURE_COLLECT_API_SCHEMA", true);
AIKIDO_GLOBAL(localhost_allowed_by_default) = GetEnvBool(envGetters, "AIKIDO_LOCALHOST_ALLOWED_BY_DEFAULT", true);
AIKIDO_GLOBAL(trust_proxy) = GetEnvBool(envGetters, "AIKIDO_TRUST_PROXY", true);
AIKIDO_GLOBAL(disk_logs) = GetEnvBool(envGetters, "AIKIDO_DISK_LOGS", false);
AIKIDO_GLOBAL(sapi_name) = sapi_module.name;
AIKIDO_GLOBAL(token) = GetEnvString("AIKIDO_TOKEN", "");
AIKIDO_GLOBAL(endpoint) = GetEnvString("AIKIDO_ENDPOINT", "https://guard.aikido.dev/");
AIKIDO_GLOBAL(config_endpoint) = GetEnvString("AIKIDO_REALTIME_ENDPOINT", "https://runtime.aikido.dev/");
AIKIDO_GLOBAL(report_stats_interval_to_agent) = GetEnvNumber("AIKIDO_REPORT_STATS_INTERVAL", 100);
}
AIKIDO_GLOBAL(token) = GetEnvString(envGetters, "AIKIDO_TOKEN", "");
AIKIDO_GLOBAL(endpoint) = GetEnvString(envGetters, "AIKIDO_ENDPOINT", "https://guard.aikido.dev/");
AIKIDO_GLOBAL(config_endpoint) = GetEnvString(envGetters, "AIKIDO_REALTIME_ENDPOINT", "https://runtime.aikido.dev/");
AIKIDO_GLOBAL(report_stats_interval_to_agent) = GetEnvNumber(envGetters, "AIKIDO_REPORT_STATS_INTERVAL", 100);
}

void LoadEnvironment() {
LoadEnvironmentFromGetters(completeEnvGetters);
}

void LoadSystemEnvironment() {
LoadEnvironmentFromGetters(systemEnvGetters);
}
4 changes: 4 additions & 0 deletions lib/php-extension/HandleFileCompilation.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#include "Includes.h"

zend_op_array* handle_file_compilation(zend_file_handle* file_handle, int type) {
if(AIKIDO_GLOBAL(disable) == true) {
return original_file_compilation_handler(file_handle, type);
}

auto& eventCacheStack = AIKIDO_GLOBAL(eventCacheStack);

// Create a new event context for file compilation
Expand Down
10 changes: 9 additions & 1 deletion lib/php-extension/HookAst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,17 @@ void insert_call_to_ast(zend_ast *ast) {
}

void aikido_ast_process(zend_ast *ast) {
auto& original_ast_process = AIKIDO_GLOBAL(originalAstProcess);

if(AIKIDO_GLOBAL(disable) == true) {
if(original_ast_process){
original_ast_process(ast);
}
return;
}

insert_call_to_ast(ast);

auto& original_ast_process = AIKIDO_GLOBAL(originalAstProcess);
if(original_ast_process){
original_ast_process(ast);
}
Expand Down
16 changes: 8 additions & 8 deletions lib/php-extension/RequestProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@ bool RequestProcessorInstance::ReportStats() {
bool RequestProcessorInstance::RequestInit() {
std::string sapiName = sapi_module.name;

if (sapiName == "frankenphp") {
if (GetEnvBoolWithAllGetters("FRANKENPHP_WORKER", false)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Calling GetEnvBoolWithAllGetters("FRANKENPHP_WORKER", ...) here can dereference AIKIDO_GLOBAL(server) via GetFrankenEnvVariable before server is initialized; guard AIKIDO_GLOBAL(server) or delay this check.

Details

✨ AI Reasoning
​The code attempts to detect a FrankenPHP warm-up request by calling GetEnvBoolWithAllGetters("FRANKENPHP_WORKER", false) early in RequestProcessor::RequestInit. GetEnvBoolWithAllGetters uses the complete set of getters including GetFrankenEnvVariable, which calls AIKIDO_GLOBAL(server).GetVar. Because the FRANKENPHP_WORKER check was moved earlier in RequestInit (before environment/server-related initialization and before LoadEnvironment/LoadLaravelEnvFile), AIKIDO_GLOBAL(server) may not be initialized yet, leading to dereferencing an uninitialized or null server object and causing a segmentation fault. This regression was introduced by moving the FRANKENPHP_WORKER check to earlier lines and by using the "all getters" helper at that point.

🔧 How do I fix it?
Add null checks before dereferencing pointers, validate array bounds before access, avoid using pointers after free/delete, don't write to string literals, and prefer smart pointers in modern C++.

Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info

AIKIDO_GLOBAL(isWorkerMode) = true;
AIKIDO_LOG_INFO("FrankenPHP worker warm-up request detected, skipping RequestInit\n");
return true;
}
}

if (sapiName == "apache2handler" || sapiName == "frankenphp") {
// Apache-mod-php and FrankenPHP can serve multiple sites per process
// We need to reload config each request to detect token changes
Expand All @@ -222,14 +230,6 @@ bool RequestProcessorInstance::RequestInit() {
}
}

if (sapiName == "frankenphp") {
if (GetEnvBool("FRANKENPHP_WORKER", false)) {
AIKIDO_GLOBAL(isWorkerMode) = true;
AIKIDO_LOG_INFO("FrankenPHP worker warm-up request detected, skipping RequestInit\n");
return true;
}
}

// Initialize the request processor only once(lazy) during RINIT because php-fpm forks the main process
// for workers and we need to load the library after the worker process is forked because
// the request processor is a go library that brings in the Go runtime, which (once initialized)
Expand Down
8 changes: 3 additions & 5 deletions lib/php-extension/include/Environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

void LoadEnvironment();

bool LoadLaravelEnvFile();

bool GetBoolFromString(const std::string& env, bool default_value);
void LoadSystemEnvironment();

bool GetEnvBool(const std::string& env_key, bool default_value);
bool LoadLaravelEnvFile();

std::string GetEnvString(const std::string& env_key, const std::string default_value);
bool GetEnvBoolWithAllGetters(const std::string& env_key, bool default_value);
1 change: 1 addition & 0 deletions lib/php-extension/include/Includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <pthread.h>

#include <functional>
#include <vector>
#include <random>
#include <string>
#include <ctime>
Expand Down
2 changes: 1 addition & 1 deletion lib/php-extension/include/php_aikido.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
extern zend_module_entry aikido_module_entry;
#define phpext_aikido_ptr &aikido_module_entry

#define PHP_AIKIDO_VERSION "1.5.1"
#define PHP_AIKIDO_VERSION "1.5.2"

#if defined(ZTS) && defined(COMPILE_DL_AIKIDO)
ZEND_TSRMLS_CACHE_EXTERN()
Expand Down
2 changes: 1 addition & 1 deletion lib/request-processor/globals/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@ func CreateServer(token string) *ServerData {
}

const (
Version = "1.5.1"
Version = "1.5.2"
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
)
2 changes: 1 addition & 1 deletion package/rpm/aikido.spec
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Name: aikido-php-firewall
Version: 1.5.1
Version: 1.5.2
Release: 1
Summary: Aikido PHP Extension
License: GPL
Expand Down
Loading