Hans Passant was correct, this was a GDI handle leak. I couldn't reproduce it on my development environment, so needed to add some logging to my server. In case it helps anyone else, here is how I got the information I needed by invoking the GetGuiResources function.
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetGuiResources(IntPtr hProcess, int uiFlags);
and using it to obtain handle counts:
var p = Process.GetCurrentProcess();
kernel = p.HandleCount;
gdiObjects = GetGuiResources(p.Handle, 0);
userObjects = GetGuiResources(p.Handle, 1);
gdiObjectsPeak = GetGuiResources(p.Handle, 2);
userObjectsPeak = GetGuiResources(p.Handle, 4);
Once that was in place, I saw that GDI objects were at the 10,000 ceiling when the crash occurred.