Skip to content

Commit 07491d1

Browse files
committed
fix(zval): Heap corruption with persistent=true #424
1 parent 6b192e8 commit 07491d1

File tree

1 file changed

+27
-2
lines changed

1 file changed

+27
-2
lines changed

src/types/zval.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,21 @@ impl Zval {
496496
/// * `val` - The value to set the zval as.
497497
/// * `persistent` - Whether the string should persist between requests.
498498
///
499+
/// # Persistent Strings
500+
///
501+
/// When `persistent` is `true`, the string is allocated from PHP's
502+
/// persistent heap (using `malloc`) rather than the request-bound heap.
503+
/// This is typically used for strings that need to survive across multiple
504+
/// PHP requests, such as class names, function names, or module-level data.
505+
///
506+
/// **Important:** The string will still be freed when the Zval is dropped.
507+
/// The `persistent` flag only affects which memory allocator is used. If
508+
/// you need a string to outlive the Zval, consider using
509+
/// [`std::mem::forget`] on the Zval or storing the string elsewhere.
510+
///
511+
/// For most use cases (return values, function arguments, temporary
512+
/// storage), you should use `persistent: false`.
513+
///
499514
/// # Errors
500515
///
501516
/// Never returns an error.
@@ -507,6 +522,9 @@ impl Zval {
507522

508523
/// Sets the value of the zval as a Zend string.
509524
///
525+
/// The Zval takes ownership of the string. When the Zval is dropped,
526+
/// the string will be released.
527+
///
510528
/// # Parameters
511529
///
512530
/// * `val` - String content.
@@ -527,9 +545,13 @@ impl Zval {
527545
self.value.str_ = ptr;
528546
}
529547

530-
/// Sets the value of the zval as a interned string. Returns nothing in a
548+
/// Sets the value of the zval as an interned string. Returns nothing in a
531549
/// result when successful.
532550
///
551+
/// Interned strings are stored once and are immutable. PHP stores them in
552+
/// an internal hashtable. Unlike regular strings, interned strings are not
553+
/// reference counted and should not be freed by `zval_ptr_dtor`.
554+
///
533555
/// # Parameters
534556
///
535557
/// * `val` - The value to set the zval as.
@@ -540,7 +562,10 @@ impl Zval {
540562
/// Never returns an error.
541563
// TODO: Check if we can drop the result here.
542564
pub fn set_interned_string(&mut self, val: &str, persistent: bool) -> Result<()> {
543-
self.set_zend_string(ZendStr::new_interned(val, persistent));
565+
// Use InternedStringEx (without RefCounted) because interned strings
566+
// should not have their refcount modified by zval_ptr_dtor.
567+
self.change_type(ZvalTypeFlags::InternedStringEx);
568+
self.value.str_ = ZendStr::new_interned(val, persistent).into_raw();
544569
Ok(())
545570
}
546571

0 commit comments

Comments
 (0)