When you pass an object to another function, you often pass a reference, not a copy. If that object is mutable and the callee is code you do not fully trust, the callee can change or delete the data behind your back. CWE-374 describes exactly this: the product sends non-cloned mutable data as an argument to a method or function.
It matters most in object-oriented code where a class holds internal state, calls a plugin, callback, listener or third party library, and then keeps using that state as if nothing happened. MITRE lists Java, C, C++ and C# among the affected platforms.
How it happens
The caller makes an assumption: "after this call, my list still holds what I put in it." That assumption holds only if the callee is well behaved. With a shared reference, any code on the other side can add, remove or overwrite elements.
Here is a vulnerable pattern in Java. The class keeps a list of allowed roles and hands it to a plugin hook.
class AccessPolicy {
private final List<String> allowedRoles = new ArrayList<>(List.of("admin", "auditor"));
void notifyPlugin(Plugin p) {
p.onPolicyCheck(allowedRoles); // live reference to internal state
}
boolean isAllowed(String role) {
return allowedRoles.contains(role);
}
}
A plugin that calls roles.add("guest") has just changed the policy. Nothing in the type system stopped it, and the class never notices.
What an attacker gains
MITRE records the consequence as an integrity problem: data in memory can be tampered with by a function that should not have touched it. In practice the damage depends on what the data controls. If the mutable object holds permissions, limits, paths or flags, a hostile or buggy callee can quietly alter the decisions the caller makes next.
How to find it
MITRE's record for this weakness lists no detection methods, so this comes down to review habits:
- Search for public methods that pass fields (not locals) to interfaces, callbacks, listeners and plugin hooks.
- Ask of each one: if the receiver mutated this, what would break?
- Check collections, arrays, date objects and builders first. They are mutable by default in most languages.
How to fix it
MITRE gives two implementation mitigations.
- Pass data that should not be altered as constant or immutable.
- Clone all mutable data before passing it to an external function. MITRE calls this the preferred mitigation, because whatever the callee does, you keep a valid copy.
The fixed version of the example:
void notifyPlugin(Plugin p) {
p.onPolicyCheck(List.copyOf(allowedRoles)); // immutable snapshot
}
List.copyOf returns an unmodifiable copy, so the plugin can read but not change anything, and your internal list is untouched. In C++, pass by const reference or by value. In C#, pass IReadOnlyList or a copy. In C, copy the buffer or pass a pointer to const data and treat that as a contract you also enforce.
Quick check
Why is a defensive copy better than just trusting the callee?
Because the copy keeps your state valid no matter what the callee does. Trust only helps when the callee is correct and honest today and stays that way after every future change.
Is passing an immutable object enough?
Yes for that object, if it is truly immutable all the way down. A list wrapper around mutable elements still lets the callee change the elements, so copy deeply when the elements are mutable too.