Skip to content

Fix use-after-free in deferred drone projectile + health-reset callbacks#7

Open
brandons209 wants to merge 1 commit into
Ivory42:Releasefrom
brandons209:fix/projectile-uaf
Open

Fix use-after-free in deferred drone projectile + health-reset callbacks#7
brandons209 wants to merge 1 commit into
Ivory42:Releasefrom
brandons209:fix/projectile-uaf

Conversation

@brandons209

Copy link
Copy Markdown

What

Fixes two use-after-free crashes where a deferred callback dereferences a wrapper whose backing handle was freed before the callback ran. On a busy server these produced a high-volume Invalid Handle exception storm (each writing a full stack trace to disk), which degraded server frame time.

1. RocketHitOwningDronePost (DroneWeapons.sp)

OnRocketTouch queues RequestFrame(RocketHitOwningDronePost, rocket) passing the projectile's APersistentObject handle. If the rocket is destroyed that same frame — e.g. it spawns inside geometry / the drone and immediately detonates — OnEntityDestroyed frees the handle, but RequestFrame is not an SDKHook, so it still fires next frame and rocket.WeaponLauncher dereferences the freed handle.

Fix: pass the entity index and re-resolve via FEntityStatics.GetEntityFromIndex (returns null once the entity has left the manager), bailing if it's gone. This is the same index-resolve idiom already used in OnRocketTouch/OnRocketEndTouch.

Note: the same EntManager_OnEntityRegistered → RequestFrame(*Post, rocket) pattern exists in the per-vehicle drone plugins' DroneRocketPost/OnRocketPost handlers (banshee/wraith/vulture/ghost-style), and should get the same treatment there.

2. ResetPlayerHealth (CustomDrones.sp)

PlayerExitVehicle's resupply path queues CreateTimer(0.1, ResetPlayerHealth, player) passing the ADronePlayer handle. If the player disconnects during the 0.1s window the AClient handle is freed and player.GetPosition() is a use-after-free.

Fix: pass the userid and re-resolve via GetClientOfUserId + FEntityStatics.GetClient, bailing if they left.

Verified

Reproduced on a dev server (rapid-fire rockets into geometry; disconnect during the resupply window). Pre-fix produced thousands of Invalid Handle exceptions; post-fix zero, with the deferred callbacks safely bailing when the entity/client is gone.

Related (not fixed here)

Separate, lower-frequency UAF during drone teardown: ADrone.Destroy → FComponentArray.ClearComponents → FEntityStatics.DestroyEntity → EntNative_Destroy → APersistentObject.Get throws Invalid Handle when a component's handle was already freed (component entity destroyed independently while still in the drone's component array). There's no safe SourcePawn-level guard (every validity check dereferences the freed handle), so it needs an architectural fix — dropping a component from its parent drone's Attachments/Weapons arrays when its entity is destroyed. Flagging in case it's useful; happy to follow up.

🤖 Generated with Claude Code

Two deferred callbacks dereference a wrapper whose backing handle can be
freed before the deferred frame runs:

- RocketHitOwningDronePost was passed the projectile's APersistentObject
  handle via RequestFrame. If the rocket is destroyed that frame (e.g. it
  spawns inside geometry/the drone and immediately detonates),
  OnEntityDestroyed frees the handle, but RequestFrame is not an SDKHook so
  it still fires next frame and rocket.WeaponLauncher dereferences a dead
  handle. Pass the entity index and re-resolve via GetEntityFromIndex
  (null once the entity leaves the manager), bailing if gone. Same idiom
  already used by OnRocketTouch/OnRocketEndTouch.

- ResetPlayerHealth's resupply timer was passed the ADronePlayer handle.
  If the player disconnects during the 0.1s delay the AClient handle is
  freed, making GetPosition() a use-after-free. Pass the userid and
  re-resolve via GetClientOfUserId/GetClient, bailing if they left.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant