Pretty sure this is a codegen bug in CubeMX v4.23.0 with latest libraries.
To reproduce: start a fresh project, select STM32F103, enable USB, choose Custom HID Device, press Generate. When run, this generates a hard fault.
We see that the 'user string' feature is enabled by default:
83 #define USBD_SUPPORT_USER_STRING 1
And this results in an additional callback added to the end of USBD_ClassTypeDef:
177 uint8_t *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
178 uint8_t *(*GetDeviceQualifierDescriptor)(uint16_t *length);
179: #if (USBD_SUPPORT_USER_STRING == 1)
180 uint8_t *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev ,uint8_t index, uint16_t *length);
This is used during enumeration, where the hard fault is triggered when the device is plugged in to the host:
390: #if (USBD_SUPPORT_USER_STRING == 1)
391 pbuf = pdev->pClass->GetUsrStrDescriptor(pdev, (req->wValue) , &len);
The code faults here because this function pointer is zero when it tries to branch.
The origin of the fault is explained by the fact that this additional field is not initialised when the callbacks are defined, and is thus zero due to partial initialisation (the member should appear after line 132):
Disabling this feature by changing the #define at top fixes the problem and the device enumerates and functions correctly.
I hope this is enough info to reproduce for a fix.