Invokable Control Element with Variadic Parameters
Description
Invokable Control Element with Variadic Parameters occurs when a function, method, or callable control element has a signature that supports a variable number of parameters or arguments (variadic parameters). While variadic functions provide flexibility, they complicate static analysis and manual code review because the actual parameters passed at each call site can vary. This makes it difficult to verify correct usage, track data flow, and identify security vulnerabilities like format string bugs or type confusion.
Risk
While primarily a code complexity issue, variadic parameters have security implications. Format string vulnerabilities (CWE-134) commonly occur in variadic functions like printf where user input can specify format specifiers. Type safety is reduced since variadic functions cannot enforce parameter types at compile time. Static analysis tools have difficulty tracking data flow through variadic functions. Code reviewers may miss security issues because the actual parameters vary per call site. Variadic functions in C/C++ can lead to undefined behavior if arguments don't match expected types.
Solution
Minimize use of variadic functions, especially in security-critical code. Use type-safe alternatives where available (e.g., variadic templates in C++, or overloaded methods). When variadic functions are necessary, validate argument counts and types at runtime. Use static analysis tools that specifically check variadic function calls. For logging and formatting, use type-safe logging frameworks. Never pass user input as format strings to variadic functions. Consider using builder patterns or option objects as alternatives to long parameter lists.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Reliability - Variadic functions complicate verification of correct usage, potentially leading to runtime errors. |
| Other | Scope: Other Quality Degradation - The flexibility of variadic parameters makes static analysis and code review more difficult. |
| Confidentiality | Scope: Confidentiality, Integrity Security Vulnerabilities - Misuse of variadic functions can lead to format string attacks or type confusion. |
Example Code
Vulnerable Code
// Vulnerable: Classic variadic function with security issues
#include <stdio.h>
#include <stdarg.h>
// Vulnerable: Custom logging with variadic parameters
void vulnerable_log(const char *format, ...) {
va_list args;
va_start(args, format);
// Vulnerable: No validation of format/args correspondence
// If format has more specifiers than args, undefined behavior
vprintf(format, args);
va_end(args);
}
// Vulnerable usage
void process_user_input(const char *user_input) {
// CRITICAL VULNERABILITY: User input as format string!
vulnerable_log(user_input); // Format string attack vector
// Attacker input: "%s%s%s%s%s" -> crashes
// Attacker input: "%x%x%x%x" -> leaks stack data
// Attacker input: "%n" -> writes to memory!
}
// Vulnerable: Variadic function with no type safety
void vulnerable_sum(int count, ...) {
va_list args;
va_start(args, count);
int sum = 0;
for (int i = 0; i < count; i++) {
// Assumes all args are int - no type checking!
sum += va_arg(args, int);
}
va_end(args);
printf("Sum: %d\n", sum);
}
// Calling with wrong types causes undefined behavior
void bad_usage() {
vulnerable_sum(3, 1, 2.5, "three"); // Types don't match!
}
# Vulnerable: Python variadic functions with security issues
def vulnerable_query(*args, **kwargs):
"""Variadic function that builds SQL queries"""
table = args[0] if args else kwargs.get('table')
columns = args[1:] if len(args) > 1 else kwargs.get('columns', ['*'])
# Vulnerable: Hard to track what's being passed
# Each call site may pass different arguments
query = f"SELECT {', '.join(columns)} FROM {table}"
# If any arg contains SQL injection payload, it's executed
return execute_query(query)
# Different call patterns make security review difficult
def usage_examples():
vulnerable_query("users", "id", "name", "email") # Positional
vulnerable_query(table="users", columns=["id", "name"]) # Keyword
vulnerable_query("users") # Just table
vulnerable_query(user_input, *user_columns) # Dynamic - dangerous!
// Vulnerable: Java varargs with type safety issues
public class VulnerableFormatter {
// Vulnerable: Variadic method accepting Object
public static String format(String template, Object... args) {
// No compile-time type checking on args
// Runtime errors if args don't match template expectations
String result = template;
for (int i = 0; i < args.length; i++) {
// Assumes args match placeholders in order
result = result.replace("{" + i + "}", String.valueOf(args[i]));
}
return result;
}
// Vulnerable: SQL building with varargs
public static String buildQuery(String table, String... columns) {
// Each call site can pass different columns
// Hard to track data flow for security analysis
return "SELECT " + String.join(", ", columns) + " FROM " + table;
}
public void vulnerableUsage(String userInput, String[] userColumns) {
// Security issue: user-controlled varargs
String query = buildQuery(userInput, userColumns);
// SQL injection possible
}
}
Fixed Code
// Fixed: Type-safe alternatives to variadic functions
// Fixed: Structured logging instead of variadic printf
typedef struct {
const char *message;
const char *level;
const char *source;
int line;
} LogEntry;
void fixed_log(const LogEntry *entry) {
// Type-safe: all fields have known types
printf("[%s] %s:%d - %s\n",
entry->level,
entry->source,
entry->line,
entry->message);
}
// Fixed: Array parameter instead of variadic
void fixed_sum(const int *values, size_t count) {
int sum = 0;
for (size_t i = 0; i < count; i++) {
sum += values[i]; // Type-safe: all elements are int
}
printf("Sum: %d\n", sum);
}
// Fixed: If variadic is needed, validate format string
void safe_log(const char *format, ...) {
// Fixed: Never accept user input as format
// Format must be a literal string from trusted code
// Validate format string at compile time if possible
#ifdef __GNUC__
__attribute__((format(printf, 1, 2)))
#endif
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
// Fixed usage: format string is never user-controlled
void fixed_process_user_input(const char *user_input) {
// Fixed: User input is data, not format
safe_log("User input received: %s", user_input);
}
// Fixed: C++ variadic templates for type safety
#include <iostream>
#include <sstream>
// Fixed: Variadic template with type safety
template<typename T>
void safe_log_impl(std::ostringstream& ss, T value) {
ss << value;
}
template<typename T, typename... Args>
void safe_log_impl(std::ostringstream& ss, T first, Args... rest) {
ss << first << " ";
safe_log_impl(ss, rest...);
}
template<typename... Args>
void safe_log(Args... args) {
// Type-safe: each argument type is known at compile time
std::ostringstream ss;
safe_log_impl(ss, args...);
std::cout << ss.str() << std::endl;
}
// Fixed: Type-safe sum with fold expressions (C++17)
template<typename... Args>
auto safe_sum(Args... args) {
// Compile-time type checking
return (args + ...);
}
// Usage - compiler catches type errors
void fixed_usage() {
safe_log("Value:", 42, "Status:", "OK"); // Type-safe
auto sum = safe_sum(1, 2, 3, 4, 5); // All same type enforced
// safe_sum(1, "two", 3); // Compile error!
}
# Fixed: Type-safe alternatives to variadic functions
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
# Fixed: Explicit parameter types instead of *args
@dataclass
class QueryParams:
table: str
columns: List[str]
conditions: Optional[Dict[str, Any]] = None
def fixed_query(params: QueryParams) -> str:
"""Type-safe query builder"""
# Clear, typed parameters
# Easy to validate and review
# Validate inputs
if not params.table.isidentifier():
raise ValueError("Invalid table name")
safe_columns = [c for c in params.columns if c.isidentifier()]
query = f"SELECT {', '.join(safe_columns)} FROM {params.table}"
return query
# Fixed: Use keyword arguments with defaults instead of varargs
def fixed_query_builder(
table: str,
columns: List[str] = None,
where: Dict[str, Any] = None,
limit: int = None
) -> str:
"""Explicit parameters instead of *args/**kwargs"""
columns = columns or ['*']
validated_columns = [c for c in columns if c.isidentifier()]
query = f"SELECT {', '.join(validated_columns)} FROM {table}"
if where:
# Use parameterized queries for conditions
pass
if limit:
query += f" LIMIT {int(limit)}"
return query
# Fixed: Builder pattern for complex configurations
class QueryBuilder:
def __init__(self, table: str):
self._table = table
self._columns: List[str] = []
self._conditions: List[str] = []
def select(self, *columns: str) -> 'QueryBuilder':
# Varargs but each call is explicit and reviewable
self._columns.extend(columns)
return self
def where(self, condition: str) -> 'QueryBuilder':
self._conditions.append(condition)
return self
def build(self) -> str:
# All components are now validated
return f"SELECT {', '.join(self._columns)} FROM {self._table}"
# Usage is clear and auditable
query = (QueryBuilder("users")
.select("id", "name", "email")
.where("active = true")
.build())
// Fixed: Type-safe alternatives in Java
import java.util.List;
import java.util.Arrays;
public class FixedFormatter {
// Fixed: Builder pattern instead of varargs
public static class MessageBuilder {
private final StringBuilder message = new StringBuilder();
public MessageBuilder append(String text) {
message.append(text);
return this;
}
public MessageBuilder append(int value) {
message.append(value);
return this;
}
public MessageBuilder append(Object value) {
message.append(String.valueOf(value));
return this;
}
public String build() {
return message.toString();
}
}
// Fixed: Explicit typed method instead of Object varargs
public static String formatUserInfo(String name, int age, String email) {
// All parameters have known types
return String.format("Name: %s, Age: %d, Email: %s", name, age, email);
}
// Fixed: Use List instead of varargs for unknown count
public static String buildQuery(String table, List<String> columns) {
// Type-safe: all columns are String
// Can validate each column
List<String> safeColumns = columns.stream()
.filter(c -> c.matches("^[a-zA-Z_][a-zA-Z0-9_]*$"))
.toList();
return "SELECT " + String.join(", ", safeColumns) + " FROM " + table;
}
// If varargs is necessary, wrap with validation
public static String safeFormat(String template, Object... args) {
// Validate count matches template placeholders
long placeholderCount = template.chars()
.filter(c -> c == '{')
.count();
if (args.length != placeholderCount) {
throw new IllegalArgumentException(
"Argument count doesn't match placeholder count");
}
String result = template;
for (int i = 0; i < args.length; i++) {
result = result.replace("{" + i + "}", String.valueOf(args[i]));
}
return result;
}
}
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping. However, variadic functions are closely related to format string vulnerabilities (CWE-134), which have numerous CVEs including CVE-2000-0573 and many others.
Related CWEs
- CWE-1120: Excessive Code Complexity (parent)
- CWE-1226: Complexity Issues (category member)
- CWE-134: Use of Externally-Controlled Format String (related security issue)
References
- MITRE Corporation. "CWE-1056: Invokable Control Element with Variadic Parameters." https://cwe.mitre.org/data/definitions/1056.html
- CISQ. "Automated Source Code Quality Measures."
- CERT C Coding Standard. "FIO30-C. Exclude user input from format strings."