I go into some detail about this phenomenon in this Q&A:
I'm rotating an object on two axes, so why does it keep twisting around the third axis?
The root issue here is that the idea that rotation is naturally/objectively separable into 3 independent components (the way we can do with translation) is a misconception.
Euler angles / Tait-Bryan angles lure us into this belief because they look a lot like a translation vector, but it's easy to show that it's false. Consider the angle triplets (180, 180, 0) and (0, 0, 180): both represent the same orientation, one purely with "x & y" rotations, the other with "purely z" rotation. Or here's another example with compounding rotations in sequence:
So, zeroing-out the z component of an angular triplet isn't enough to guarantee you don't get roll.
What to do instead depends on the exact behaviour you want. You could try for instance track a pitch & yaw value independently, and apply the total all at once rather than incrementally compounding rotations:
// in Start(), initialize your x & y angles to match the camera's initial orientation.
pitch = transform.eulerAngles.x;
yaw = transform.eulerAngles.y;
// In Update, accumulate rotational change in these axes:
pitch += input.gyro.rotationRateUnbiased.x * Mathf.RadToDeg() * Time.deltaTime;
yaw += input.gyro.rotationRateUnbiased.y * Mathf.RadToDeg() * Time.deltaTime;
// Apply the result all at once:
transform.eulerAngles = new Vector3(pitch, yaw, 0);
Or you could try rotating the forward / look vector, then construct a "roll-free" orientation that looks in that direction:
Vector3 angularDelta = input.gyro.rotationRateUnbiased * Time.deltaTime;
Vector3 newForward = transform.forward + Vector3.Cross(transform.forward, angularDelta);
transform.rotation = Quaternion.LookRotation(newForward);