Welcome to the Developer Update for 3 October 2024!
Today’s thumbnail features Magic AI-Art: Dimensions by Niko*.
Our last Developer Update was back on the 19th of September! Did you miss it? Go check it out here!
Announcements
Spookality Submissions Are Now Open!
You have until October 14th to submit your spooky creations to this year’s Halloween-themed jam!
Check out our blog post for more information on the themes and categories this year.
As a note, there are two separate world categories this year in addition to the avatar jam – make sure to check both of them out!
We’re Streaming Tomorrow!
Tomorrow (October 4) at 2PM PDT, we’ll be streaming on our Twitch channel. Don’t expect any big announcements, though! We’ll mostly be hanging out, playing with Stickers and Sharing, and answering a few questions.
See you there!
Udon Code Signing
The eagle-eyed among you might have spotted the following notes on the latest 3.7.2-beta.2
SDK changelog:
Added Udon Code Signing!
- Any worlds uploaded with this SDK (or newer) receive a server-side signature. This helps ensure only authentic (unmodified) versions of your world can be loaded into VRChat.
- This makes it significantly harder for malicious players to cheat or bypass scripts in your worlds.
- Each platform (PC, Android, iOS) has a different signature, so you need to upgrade all of your world's platforms.
That tells you everything you need to know, but if you want a bit more detail, then read on!
Note that for security reasons we may still skip over some super-low-level implementation details here.
The Exploit
What attack are we trying to prevent here? Internally we call this the “Asset Bundle Cache Swap” pattern, and it’s been abused by malicious users to inject custom Udon scripts, bypass restrictions (including for creator economy benefits) and utilized for harassment.
On a high-level it works by downloading a world and then modifying the copy that gets stored on your disk. This copy is generally used to avoid re-downloading a world every time. However, malicious users could modify this cache to their liking before loading data from it – locally only, of course, but that’s enough to enable abuse.
As an aside, since nothing here modifies VRChat itself, this is unrelated to EAC.
Unity’s Built-in Prevention
Unity itself provides a CRC checksum over cached assets that can be used to validate them. Unfortunately, this is only a 32-bit value, and as such can easily be cracked by modern PCs. To be fair, it is less used for security checks and more used to identify data corruption (“bad disk”) issues. Nevertheless, since 3.7.2-beta.2
we now employ this CRC-check too.
Unfortunately, due to the way Unity’s assetbundle loading works, it becomes very hard to validate a bundle as a whole using a stronger signature. So we looked for ways we can validate the contents of the bundle instead (at least the parts that matter).
Code Signing
All Udon scripts you have in your world get compiled into Udon assembly, which is stored as a serialized byte array. In other words, regardless of where your Udon comes from (prefabs, custom U#, node graph, CyanTrigger, etc.), what gets uploaded is actually just a compressed bundle of bytes.
When the new SDK finalizes an upload, we utilize some modern cryptography to create a signature over those byte arrays. To do this, a signing keypair is generated, consisting of a signing key (can be used to create signatures) and a verify key (can only be used to validate, but not create signatures). The signatures are embedded into the world at upload-time, while the verify key is stored on our API. The signing key is simply thrown away, discarded. Every upload generates a new one, which is fine since signing is so fast.
Think of this like a stamp of approval: when you upload a world, your PC will stamp off every piece of Udon you have, and then put a printout of that stamp on our API. VRChat can then check that the stamp on every script matches the printout, but obviously a printout can’t be used to stamp any new scripts.
For the nerds ahem enthusiasts in the audience: We calculate an ed25519 signature over the SHA-512 hash of each individual signable element and serialize it alongside the data itself. All of this is implemented in Rust for performance, by the way. This way, calculating all the signatures usually takes under 10 milliseconds, even for larger worlds!
When the VRChat client now loads a world, it will see both the verify key (which isn’t in the cache!) and all the signatures, and can thus validate every Udon program as it loads in. Since the signing key is gone, there is no way to forge these signatures. Scripts without a signature will be treated as an error too, so there is no way to inject malicious Udon code anymore.
When validation of a script fails, you will be sent home or to the error world. The cached bundle will also be deleted, so the next time you visit that world it will re-download it. This is mostly in case you actually run into something like disk corruption that could trigger a validation failure without malicious intent.
As a funny aside, we called this library vrc_fast_crypto.dll (for “cryptography”, to be clear), which made the Community team cringe because they were afraid folks would see it and assume we were getting into “Crypto” (Currency) with this one. Nope!
Perfect Security?
As usual with these security-related changes, this is not perfect. We are fully aware that this does not cover all our bases (though we’ll leave it to y’all to figure out what’s missing). It does, however, add a significant hurdle. And as always, we will continue to work towards strengthening our defenses as time goes on.
If you want to report a security issue, or think you have found a problem in our methods, you can report it confidentially via this form. We take such reports very seriously and will work with you to address them.
Ongoing Development
Manage and Hide Your Badges
Did you know that there are currently five badges obtainable in VRChat? Currently, though, it’s only possible to see the first three badges someone has earned on their profile.
With this update, people with more than three will have a “View All” button to see a gallery of all their obtained badges. On your own profile you can go to the badge gallery to manage your badges. From the gallery you can select up to three badges to showcase on your profile.
Someone looking at your profile can click “View All” to see a full list of badges.
Note that you cannot currently rearrange the profile badges, this should be coming at a later date.
Hiding Badges
If you wish to completely hide a badge, you can click on the badge in your gallery then select “Hide badge” and it will be completely hidden from other people.
Web Updates!
Back at it again with more updates from the web team! Changes available now at vrchat.com/home include:
-
Avatars now have previews when linked on platforms like Discord or Twitter:
-
Avatar pages now display performance ratings:
-
Impostor information is now shown for iOS.
-
Fixed a bug which would sometimes incorrectly show your friends as hanging out together offline.
- I mean… it could be true, potentially, but we promise we have no way to know that.
-
Stickers (currently an Open Beta feature) can now be managed from the Gallery page.
-
Group invites whose notifications you dismissed can now be declined from the Group’s page instead of lost in the ether.
-
Several fixes related to upcoming features.
-
Fixed a bug which caused the OAuth sign-in page (for Furality, the VRChat Wiki, etc.) to break.
Impostor Generator Updates
Just in time for Spookality, we’ve started applying auto-configuration rules to separate large, common extra parts, such as tails, ears, hats, ponytails, capes, horns and antlers!
The impostor generator searches through the armature hierarchy, pattern-matching objects’ names against its word banks and ensuring that any matched parts are large enough to be worth separating. Parts that pass the tests are treated just as if they had been manually added to a VRCImpostorSettings script’s Extra Child Transforms list.
(Don’t worry, if the avatar already had a VRCImpostorSettings script, we’ll skip this auto-matching behavior and use your choices instead.)
Separating animated parts in the impostor lets them follow the motion of their roots on the real avatar, making them more expressive.
(That should get your ears perked up and your tail wagging.)
More importantly, it helps us preserve visual fidelity on the parts where it matters most. Pi volunteered their Halloween costume as tribute to show us why this is important:
Spooky! But not really the kind of spooky we’re into.
If you’d like this fix, please delete and regenerate impostors on the website for any avatars you think would benefit from this update. Auto-generation of impostors is ongoing, but generating them manually is the fastest way to jump to the front of the line and make sure you look your best to other users!
We’re working continuously to make impostor avatars the best they can be, and we need your help! Please send us the avatar IDs via a support ticket if you encounter an impostor that’s broken or not a good representation of the original.
Hand Tracking Update
We’ve got 2 new features for hand tracking!
Quick Menu Openers
To resolve a conflict where some VR streaming apps used the same gesture to open the SteamVR dashboard that we used to open the quick menu, we have introduced two new ways to open the Quick Menu:
Circle-Key Opener
A wrist-mounted virtual wearable interface that opens the Quick Menu when you pilot the icon into the circular socket. Only requires one hand to use.
Push Button
A wrist-mounted virtual wearable interface that opens the Quick Menu when you press the button with the opposing hand’s index finger. Requires two hands to operate.
Action Menu
Previously, the Action Menu was unavailable when using hand tracking. You can now place and open it by looking at your right palm, and pinching the top of your thumb and pinky fingertip together. It will show you a preview of the drop location, and place itself there when you release.
See both of them below:
Conclusion
That’s it for now! The next update is in two weeks: October 17! See you then!