fix: check arg length (external liquid renderer)

This commit is contained in:
Benjamin Nussbaum 2025-11-06 14:17:36 +01:00
parent 964d015087
commit ca9532b6e9
2 changed files with 62 additions and 3 deletions

View file

@ -11,6 +11,7 @@ use App\Liquid\Filters\StandardFilters;
use App\Liquid\Filters\StringMarkup;
use App\Liquid\Filters\Uniqueness;
use App\Liquid\Tags\TemplateTag;
use App\Services\PluginImportService;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -372,14 +373,14 @@ class Plugin extends Model
* @param array $context The render context data
* @return string The rendered HTML
*
* @throws LiquidException
* @throws Exception
*/
private function renderWithExternalLiquidRenderer(string $template, array $context): string
{
$liquidPath = config('services.trmnl.liquid_path');
if (empty($liquidPath)) {
throw new LiquidException('External liquid renderer path is not configured');
throw new Exception('External liquid renderer path is not configured');
}
// HTML encode the template
@ -389,9 +390,12 @@ class Plugin extends Model
$jsonContext = json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($jsonContext === false) {
throw new LiquidException('Failed to encode render context as JSON: '.json_last_error_msg());
throw new Exception('Failed to encode render context as JSON: '.json_last_error_msg());
}
// Validate argument sizes
app(PluginImportService::class)->validateExternalRendererArguments($encodedTemplate, $jsonContext, $liquidPath);
// Execute the external renderer
$process = Process::run([
$liquidPath,

View file

@ -381,4 +381,59 @@ class PluginImportService
'sharedLiquidPath' => $sharedLiquidPath,
];
}
/**
* Validate that template and context are within command-line argument limits
*
* @param string $template The liquid template string
* @param string $jsonContext The JSON-encoded context
* @param string $liquidPath The path to the liquid renderer executable
* @return void
*
* @throws Exception If the template or context exceeds argument limits
*/
public function validateExternalRendererArguments(string $template, string $jsonContext, string $liquidPath): void
{
// MAX_ARG_STRLEN on Linux is typically 131072 (128KB) for individual arguments
// ARG_MAX is the total size of all arguments (typically 2MB on modern systems)
$maxIndividualArgLength = 131072; // 128KB - MAX_ARG_STRLEN limit
$maxTotalArgLength = $this->getMaxArgumentLength();
// Check individual argument sizes (template and context are the largest)
if (strlen($template) > $maxIndividualArgLength || strlen($jsonContext) > $maxIndividualArgLength) {
throw new Exception('Context too large for external liquid renderer. Reduce the size of the Payload or Template.');
}
// Calculate total size of all arguments (path + flags + template + context)
// Add overhead for path, flags, and separators (conservative estimate: ~200 bytes)
$totalArgSize = strlen($liquidPath) + strlen('--template') + strlen($template)
+ strlen('--context') + strlen($jsonContext) + 200;
if ($totalArgSize > $maxTotalArgLength) {
throw new Exception('Context too large for external liquid renderer. Reduce the size of the Payload or Template.');
}
}
/**
* Get the maximum argument length for command-line arguments
*
* @return int Maximum argument length in bytes
*/
private function getMaxArgumentLength(): int
{
// Try to get ARG_MAX from system using getconf
$argMax = null;
if (function_exists('shell_exec')) {
$result = @shell_exec('getconf ARG_MAX 2>/dev/null');
if ($result !== null && is_numeric(trim($result))) {
$argMax = (int) trim($result);
}
}
// Use conservative fallback if ARG_MAX cannot be determined
// ARG_MAX on macOS is typically 262144 (256KB), on Linux it's usually 2097152 (2MB)
// We use 200KB as a conservative limit that works on both systems
// Note: ARG_MAX includes environment variables, so we leave headroom
return $argMax !== null ? min($argMax, 204800) : 204800;
}
}