migration: reject out-of-range migration state boundary (#875)

Reject device_state == VFIO_USER_DEVICE_NUM_STATES to prevent
out-of-bounds access to the migration state transition table.
Also add a regression test for the boundary case.

Signed-off-by: Siddharth C <siddharthcibi@icloud.com>
Reviewed-by: Thanos Makatos <thanos.makatos@nutanix.com>
diff --git a/lib/migration.c b/lib/migration.c
index 02c29c1..f995fca 100644
--- a/lib/migration.c
+++ b/lib/migration.c
@@ -268,7 +268,7 @@
     uint32_t state;
     ssize_t ret = 0;
     
-    if (device_state > VFIO_USER_DEVICE_NUM_STATES) {
+    if (device_state >= VFIO_USER_DEVICE_NUM_STATES) {
         return ERROR_INT(EINVAL);
     }
     
diff --git a/test/py/libvfio_user.py b/test/py/libvfio_user.py
index 92188b0..7bddb3d 100644
--- a/test/py/libvfio_user.py
+++ b/test/py/libvfio_user.py
@@ -237,6 +237,7 @@
 VFIO_USER_DEVICE_STATE_RUNNING_P2P = 5
 VFIO_USER_DEVICE_STATE_PRE_COPY = 6
 VFIO_USER_DEVICE_STATE_PRE_COPY_P2P = 7
+VFIO_USER_DEVICE_NUM_STATES = 8
 
 VFIO_DEVICE_FEATURE_MASK = 0xffff
 VFIO_DEVICE_FEATURE_GET = (1 << 16)
diff --git a/test/py/test_migration.py b/test/py/test_migration.py
index d423119..3fc3d19 100644
--- a/test/py/test_migration.py
+++ b/test/py/test_migration.py
@@ -322,6 +322,11 @@
     transition_to_migr_state(0xabcd, expect=errno.EINVAL)
 
 
+def test_migration_num_states_boundary():
+    transition_to_migr_state(VFIO_USER_DEVICE_NUM_STATES,
+                             expect=errno.EINVAL)
+
+
 def test_migration_failed_callback():
     setup_fail_callbacks(0xbeef)
     transition_to_migr_state(VFIO_USER_DEVICE_STATE_RUNNING, expect=0xbeef)