The invalid pot sherd exploit and how it worked

videogamesm12

Well-known member
Developer
Senior Admin
341
IGN
videogamesm12

What was the exploit?​

Invalid pot sherd was an exploit which took advantage of an oversight in how the game processes decorated pot sherds for use in rendering to crash anyone who was within range. Decorated pots have sherds that are stored in a list of specially-encoded strings called identifiers. When the game needs to render a decorated pot in your hand, it loads the sherd data from the item's NBT beforehand and then renders the item. Mojang made a mistake with the loading system where an incorrectly formatted identifier will result in the game throwing an error, causing the game to crash with a custom error called an InvalidIdentifierException. The bug was introduced in the game with the 1.19.4 update and saw some brief exploitation before it was patched by Mojang in 1.20.2.

It was first discovered in late June 2023 on an offshoot server before eventually leaking to the exploit community at large. Despite this, it never affected the original TotalFreedom as that server never ran anything newer than 1.17.1, so it wasn't ever documented there. By the time Rebooted was established it had already been known about for months and had already been patched with CFX and Scissors, but it was still documented in an admin-only thread on this forum, though without much of a technical deep-dive since documentation at the admin level focused more on teaching staff how to deal with it when they encounter it instead of teaching them about the inner workings of the exploit.

This technical explanation was written with the codebase of 1.20 in mind. It shouldn't be applied to anything newer as the game was significantly rewritten to use data components for items instead of NBT in 1.20.5+.

How exactly did it work?​

In Minecraft, an identifier (also known as a resource location) is an object that is often used to store information like the location of a model, the identifier for a block, and more. This system was introduced in update 1.6.1 and has been greatly expanded on since then. They are essentially an object consisting of two values: a namespace and a path. The namespace dictates what something is coming from, while the path dictates where something needs to go.

The game is very strict as for what can be in an identifier's namespace and a path - namespaces can only have the alphabet in lowercase, numbers 0-9, an underscore, a dash, or a period while paths are only very slightly more lenient by also allowing a forward slash. These are checked every time an identifier is created, whether it be through a constructor (if you don't know what a constructor is, read my explanation from this thread) or a function/method in the code. If the namespace or path contain invalid characters, then it bombs out with an error called an InvalidIdentifierException. Since a lot of things in the game take information that players can manipulate like item attributes and potion effects, Mojang added special functions in the code called "tryParse" and "of", which creates the identifier but will catch this error and return null if the input is invalid, preventing possible crashes and other strange behavior later down the line:

Java:
@Nullable
public static Identifier tryParse(String id) {
    try {
        return new Identifier(id);
    } catch (InvalidIdentifierException var2) {
        return null;
    }
}

@Nullable
public static Identifier of(String namespace, String path) {
    try {
        return new Identifier(namespace, path);
    } catch (InvalidIdentifierException var3) {
        return null;
    }
}

Item rendering works in steps to make sure things display correctly. When the game wants to render a decorated pot, it calls a function beforehand that copies data from the item into a "mini" Decorated Pot used exclusively for rendering. Within this function is a call to another function used to load data from an NBT tag called fromNbt, shown below:
Java:
public static DecoratedPotBlockEntity.Sherds fromNbt(@Nullable NbtCompound nbt) {
    if (nbt != null && nbt.contains("sherds", NbtElement.LIST_TYPE)) {
        NbtList nbtList = nbt.getList("sherds", NbtElement.STRING_TYPE);
        return new DecoratedPotBlockEntity.Sherds(getSherd(nbtList, 0), getSherd(nbtList, 1), getSherd(nbtList, 2), getSherd(nbtList, 3));
    } else {
        return DEFAULT;
    }
}
It's a bit overwhelming, but I'll help break it down. It checks to make sure that the NBT exists and that it contains an NBT list named "sherds" (in 1.19.4 it's named "shards" instead). It then gets this list and tries to create an instance of DecoratedPotBlockEntity.Sherds using the results of calling the getSherd function 4 times with different parameters. We're getting closer. Let's take a look at what this function has to say.

Java:
private static Item getSherd(NbtList list, int index) {
    if (index >= list.size()) {
        return Items.BRICK;
    } else {
        NbtElement nbtElement = list.get(index);
        return Registries.ITEM.get(new Identifier(nbtElement.asString()));
    }
}
Now we're talking. The code checks to make sure that the index is within range and if it is, tries to get an item type by loading the string value at the given index as an Identifier. But wait, something's amiss - it's using the constructor for an identifier raw without any safety checks around it. This normally isn't an issue during survival gameplay as decorated pot sherds use valid identifiers, but this initial feeling of safety goes right out the door when creative mode and user-customizable item NBT gets involved where they can set anything to any value without any restrictions. So, what happens when the game gets told to get a sherd which says something like I AM GOING TO RAID YOU AND STEAL YOUR TELEVISION! GET REKT? Well, the game calls the unsafe constructor to create an identifier which will check the namespace and path, find invalid characters, and-


InvalidIdentifierException, followed by a message complaining about the malformed namespace/path. This doesn't get caught anywhere, resulting in a game crash.

Code:
net.minecraft.class_151: Non [a-z0-9/._-] character in path of location: minecraft:ouch!
    at knot//net.minecraft.class_2960.method_45137(class_2960.java:252)
    at knot//net.minecraft.class_2960.<init>(class_2960.java:47)
    at knot//net.minecraft.class_2960.<init>(class_2960.java:51)
    at knot//net.minecraft.class_2960.<init>(class_2960.java:56)
    at knot//net.minecraft.class_8172.method_11014(class_8172.java:68)
    at knot//net.minecraft.class_8172.method_49196(class_8172.java:139)
    at knot//net.minecraft.class_756.method_3166(class_756.java:133)
    at knot//net.minecraft.class_918.method_23179(class_918.java:139)
    at knot//net.minecraft.class_916.method_3996(class_916.java:101)
    at knot//net.minecraft.class_916.method_3936(class_916.java:17)
    at knot//net.minecraft.class_898.method_3954(class_898.java:141)
    at knot//net.minecraft.class_761.method_22977(class_761.java:1572)
    at knot//net.minecraft.class_761.method_22710(class_761.java:1322)
    at knot//net.minecraft.class_757.method_3188(class_757.java:1100)
    at knot//net.minecraft.class_757.method_3192(class_757.java:872)
    at knot//net.minecraft.class_310.method_1523(class_310.java:1218)
    at knot//net.minecraft.class_310.method_1514(class_310.java:801)
    at knot//net.minecraft.client.main.Main.main(Main.java:237)
    at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:480)
    at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74)
    at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23)
    at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:105)
    at org.prismlauncher.EntryPoint.listen(EntryPoint.java:129)
    at org.prismlauncher.EntryPoint.main(EntryPoint.java:70)

Interestingly, this bug also affects the server side because it calls the same functions that the client-side item renderer does to load the pot, but there is no actual way to cause the server to crash with it since attempts to place it either kick the player or places a blank decorated pot and spews an error into the logs. I couldn't get it to crash, even with WorldEdit schematics.

How do I know if my game crashed because of this exploit?​

Yet again, we can refer to the crash report. Here's an example of a crash report generated as a result of this exploit:

Code:
---- Minecraft Crash Report ----
// My bad.

Time: 2025-06-30 16:14:20
Description: Rendering entity in world

z: Non [a-z0-9_.-] character in namespace of location: this_is_rad!:wow!
    at add.c(SourceFile:232)
    at add.<init>(SourceFile:47)
    at add.<init>(SourceFile:51)
    at add.<init>(SourceFile:56)
    at czr.a(SourceFile:68)
    at czr.a(SourceFile:139)
    at fhs.a(SourceFile:133)
    at foc.a(SourceFile:139)
    at foc.a(SourceFile:280)
    at fic.a(SourceFile:163)
    at frg.a(SourceFile:64)
    at frm.a(SourceFile:35)
    at frg.a(SourceFile:42)
    at frg.a(SourceFile:15)
    at fof.a(SourceFile:145)
    at fsf.a(SourceFile:66)
    at fsf.a(SourceFile:43)
    at fnc.a(SourceFile:141)
    at fie.a(SourceFile:1572)
    at fie.a(SourceFile:1322)
    at fhz.a(SourceFile:1100)
    at fhz.a(SourceFile:872)
    at emh.f(SourceFile:1218)
    at emh.e(SourceFile:801)
    at net.minecraft.client.main.Main.main(SourceFile:237)
    at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:105)
    at org.prismlauncher.EntryPoint.listen(EntryPoint.java:129)
    at org.prismlauncher.EntryPoint.main(EntryPoint.java:70)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Thread: Render thread
Stacktrace:
    at add.c(SourceFile:232)
    at add.<init>(SourceFile:47)
    at add.<init>(SourceFile:51)
    at add.<init>(SourceFile:56)
    at czr.a(SourceFile:68)
    at czr.a(SourceFile:139)
    at fhs.a(SourceFile:133)
    at foc.a(SourceFile:139)
    at foc.a(SourceFile:280)
    at fic.a(SourceFile:163)
    at frg.a(SourceFile:64)
    at frm.a(SourceFile:35)
    at frg.a(SourceFile:42)
    at frg.a(SourceFile:15)
    at fof.a(SourceFile:145)
    at fsf.a(SourceFile:66)
    at fsf.a(SourceFile:43)
    at fnc.a(SourceFile:141)
    at fie.a(SourceFile:1572)
    at fie.a(SourceFile:1322)
    at fhz.a(SourceFile:1100)

-- Entity being rendered --
Details:
    Entity Type: minecraft:player (fhk)
    Entity ID: 690
    Entity Name: videogamesm12
    Entity's Exact location: 153.31, 78.00, 149.96
    Entity's Block location: World: (153,78,149), Section: (at 9,14,5 in 9,4,9; chunk contains blocks 144,-64,144 to 159,319,159), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,-64,0 to 511,319,511)
    Entity's Momentum: 0.00, -0.08, 0.00
    Entity's Passengers: []
    Entity's Vehicle: null
Stacktrace:
    at fnc.a(SourceFile:141)
    at fie.a(SourceFile:1572)
    at fie.a(SourceFile:1322)
    at fhz.a(SourceFile:1100)
    at fhz.a(SourceFile:872)
    at emh.f(SourceFile:1218)
    at emh.e(SourceFile:801)
    at net.minecraft.client.main.Main.main(SourceFile:237)
    at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:105)
    at org.prismlauncher.EntryPoint.listen(EntryPoint.java:129)
    at org.prismlauncher.EntryPoint.main(EntryPoint.java:70)

-- Renderer details --
Details:
    Assigned renderer: fsf@75556598
    Location: -3.54,-1.35,-1.84 - World: (-4,-2,-2), Section: (at 12,14,14 in -1,-1,-1; chunk contains blocks -16,-64,-16 to -1,319,-1), Region: (-1,-1; contains chunks -32,-32 to -1,-1, blocks -512,-64,-512 to -1,319,-1)
    Rotation: -62.550022
    Delta: 0.06000805
Stacktrace:
    at fnc.a(SourceFile:141)
    at fie.a(SourceFile:1572)
    at fie.a(SourceFile:1322)
    at fhz.a(SourceFile:1100)
    at fhz.a(SourceFile:872)
    at emh.f(SourceFile:1218)
    at emh.e(SourceFile:801)
    at net.minecraft.client.main.Main.main(SourceFile:237)
    at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:105)
    at org.prismlauncher.EntryPoint.listen(EntryPoint.java:129)
    at org.prismlauncher.EntryPoint.main(EntryPoint.java:70)

-- Affected level --
Details:
    All players: 1 total; [fhk['videogamesm12'/690, l='ClientLevel', x=153.31, y=78.00, z=149.96]]
    Chunk stats: 961, 615
    Level dimension: minecraft:overworld
    Level spawn location: World: (0,70,0), Section: (at 0,6,0 in 0,4,0; chunk contains blocks 0,-64,0 to 15,319,15), Region: (0,0; contains chunks 0,0 to 31,31, blocks 0,-64,0 to 511,319,511)
    Level time: 2714 game time, 2714 day time
    Server brand: vanilla
    Server type: Integrated singleplayer server
Stacktrace:
    at fdj.a(SourceFile:455)
    at emh.d(SourceFile:2406)
    at emh.e(SourceFile:820)
    at net.minecraft.client.main.Main.main(SourceFile:237)
    at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:105)
    at org.prismlauncher.EntryPoint.listen(EntryPoint.java:129)
    at org.prismlauncher.EntryPoint.main(EntryPoint.java:70)

-- Last reload --
Details:
    Reload number: 1
    Reload reason: initial
    Finished: Yes
    Packs: vanilla

Crash reports advanced pretty decently in later versions so that the error message is separated from what the game was doing at the time of the crash, making it far easier to understand what was going on. However, the cause of the crash is more difficult to figure out depending on your environment. Even still, I've found that these clues when combined indicate the cause of the crash:
  • The error complains about a character that isn't in a specific pattern in a path or namespace of something
    This error in particular is not unique to the exploit as historically there have been a couple other exploits that abuse the same issue, but it is a good hint since it helps narrow down why it happened.

  • The stacktrace contains these numbers in order: 47, 51, 56, 68, 139 (if on 1.19.4) OR 47, 51, 56, 101, 89, 63 (if on 1.20).
    Minecraft crash stacktraces are a pain in the ass to read in modded and unmodded environments. These numbers in particular are the line numbers of the faulty code in order of execution. If you are feeling really brave, you can also go line-by-line and compare the mappings with this website with the version being used at the time to see if it makes sense.
If these two clues add up, then the game crashed due to the exploit. Now, it's time to figure out what your next move is. Obviously you're going to crash whenever you try to render the pot, but if you can essentially vaccinate your client against the exploit with a patch, then you could safely deal with the exploit without crashing. Let's talk about that.

How do I protect yourself against this exploit?​

Since this exploit has been known about for quite some time, it has been patched on both the client and server-side for quite some time through both official and unofficial means. Clients can either update to 1.20.2 or install a Fabric mod I wrote called CFX (if you're running on 1.20.x). Servers can update to 1.20.2 as well or switch to Scissors for 1.20.1, though this will not stop unpatched clients from crashing.

With your client effectively vaccinated against the exploit, now it's time to deal with whoever crashed your game.

How do I know who crashed my game?​

To quote my original documentation from September 2023:
If a player crashed you by just holding the item while in your vicinity, you can actually see who crashed you by opening the crash report and scrolling down to "Entity being rendered".

Code:
-- Entity being rendered --
Details:
    Entity Type: minecraft:player (net.minecraft.class_745)
    Entity ID: 244
    Entity Name: videogamesm12
    Entity's Exact location: 25.15, 87.00, -21.63
    Entity's Block location: World: (25,87,-22), Section: (at 9,7,10 in 1,5,-2; chunk contains blocks 16,-64,-32 to 31,319,-17), Region: (0,-1; contains chunks 0,-32 to 31,-1, blocks 0,-64,-512 to 511,319,-1)
    Entity's Momentum: -0.03, 0.00, 0.07
    Entity's Passengers: []
    Entity's Vehicle: null

Furthermore, players with the exploit patched in their client are able to view the NBT of the item in their inventory. At the bare minimum, this is an example of what the payload looks like:
1693792786917.png

Conclusion​

Thanks again for reading this thread. I had wanted to document a more recent exploit and this one seemed simple enough to document without spending an entire afternoon trying to dig through increasingly ancient codebases. What exploit should I document next? There's plenty of exploits to talk about.