From nobody Thu Oct 2 18:15:52 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 100FF322A30; Fri, 12 Sep 2025 16:58:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757696339; cv=none; b=L9TJbBGJnl6BrIo7bkwy5ttVbEJWjRPCHkrbRAWw2wVoOdcJ7zM4Ncly9mJqfXVb/W4A9IbszdaKOAGVrvUkUv2/QCNoEQVr0LHlf+BFRE1PDZRmGCg8/uT+MRfY9e2bIxQf6z1QaBc3ZbYz18OJL/QTa+iQqFIn4kJdTR8pn48= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757696339; c=relaxed/simple; bh=mjY3RZuvVJFAi4+g/6jHHB9YpmT8msFtkiUvM6rMXjg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Kb3ES1d+oZSlAOPYuuTFWy06Fd4FNWEozZA5AMwURGSuDHN2nrY3J44Nuho3P4OxltZIELG6KAGeHjf8MGltyuXz+COR5YTyPp2QgB7rV3Vj/+RNtYtK76l0zziGD16c0e1kfAiwBDPPlX5gQYmDg1TvwoFA+oHvtQF+Fumva8o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=DgG3rQlX; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="DgG3rQlX" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 47E3AC4CEF9; Fri, 12 Sep 2025 16:58:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757696338; bh=mjY3RZuvVJFAi4+g/6jHHB9YpmT8msFtkiUvM6rMXjg=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=DgG3rQlXlit6PLzoKTo6Ofku50Sj8BQX/e+8Fv9QwUJOEceKKmxQANZFvKlF6l/iD PfY4T6BxscfbyfASUjgmv9VK8cqwT7A0eKVzu+ZmXQMzP6bETgTeJyUhqpBBQd+E5G Cq6NAWs2nYA0IkPiS+U9cRu+8LyurgRM0GCxtw/RE3c/jljMnwh6pg8OLkTAdynckI WE28cY6gcsZKuiUqI6NzZBJ+UM0ffMGxFSbCdtkB7waOx60cBDzeD2YjlKUVJRNbSD MBn7YL6LBbLn3pCYa7CuWtAwDaIIzVyGyWcQUOWdZ1YTAMISB0JdZFBgAjrRyQRwdS wDmM9AvO7n9yg== From: Benjamin Tissoires Date: Fri, 12 Sep 2025 18:58:49 +0200 Subject: [PATCH v3 1/3] selftests/hid: hidraw: add more coverage for hidraw ioctls Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250912-b4-hidraw-ioctls-v3-1-cd2c6efd8c20@kernel.org> References: <20250912-b4-hidraw-ioctls-v3-0-cd2c6efd8c20@kernel.org> In-Reply-To: <20250912-b4-hidraw-ioctls-v3-0-cd2c6efd8c20@kernel.org> To: Jiri Kosina , Shuah Khan , Arnd Bergmann Cc: linux-input@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Benjamin Tissoires X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1757696335; l=11775; i=bentiss@kernel.org; s=20230215; h=from:subject:message-id; bh=mjY3RZuvVJFAi4+g/6jHHB9YpmT8msFtkiUvM6rMXjg=; b=y13C88NBDjIqGm8GCSb3ycZZMUGhykpY1uMKxFfKkziEkF0EogtZodG0FbFUB42Pb2gAPaFme Hm7kr5vVdIOAH4LH7QhcQWidrOvMUvZAIepMlFA97zZ7LVBcArGAfjb X-Developer-Key: i=bentiss@kernel.org; a=ed25519; pk=7D1DyAVh6ajCkuUTudt/chMuXWIJHlv2qCsRkIizvFw= Try to ensure all ioctls are having at least one test. Most of the scaffholding has been generated by claude-4-sonnet and then carefully reviewed. Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hid_common.h | 6 + tools/testing/selftests/hid/hidraw.c | 346 +++++++++++++++++++++++++++= ++++ 2 files changed, 352 insertions(+) diff --git a/tools/testing/selftests/hid/hid_common.h b/tools/testing/selft= ests/hid/hid_common.h index f77f69c6657d0f0f66beb3b50bf4b126f6f63348..8085519c47cb505b901ac80f208= 7dc9a1aa2b9c0 100644 --- a/tools/testing/selftests/hid/hid_common.h +++ b/tools/testing/selftests/hid/hid_common.h @@ -230,6 +230,12 @@ static int uhid_event(struct __test_metadata *_metadat= a, int fd) break; case UHID_SET_REPORT: UHID_LOG("UHID_SET_REPORT from uhid-dev"); + + answer.type =3D UHID_SET_REPORT_REPLY; + answer.u.set_report_reply.id =3D ev.u.set_report.id; + answer.u.set_report_reply.err =3D 0; /* success */ + + uhid_write(_metadata, fd, &answer); break; default: TH_LOG("Invalid event from uhid-dev: %u", ev.type); diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests= /hid/hidraw.c index 821db37ba4bbef82e5cf4b44b6675666f87a12ad..6d61d03e2ef05e1900fe5a3938d= 93421717b2621 100644 --- a/tools/testing/selftests/hid/hidraw.c +++ b/tools/testing/selftests/hid/hidraw.c @@ -2,6 +2,9 @@ /* Copyright (c) 2022-2024 Red Hat */ =20 #include "hid_common.h" +#include +#include +#include =20 /* for older kernels */ #ifndef HIDIOCREVOKE @@ -215,6 +218,349 @@ TEST_F(hidraw, write_event_revoked) pthread_mutex_unlock(&uhid_output_mtx); } =20 +/* + * Test HIDIOCGRDESCSIZE ioctl to get report descriptor size + */ +TEST_F(hidraw, ioctl_rdescsize) +{ + int desc_size =3D 0; + int err; + + /* call HIDIOCGRDESCSIZE ioctl */ + err =3D ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed"); + + /* verify the size matches our test report descriptor */ + ASSERT_EQ(desc_size, sizeof(rdesc)) + TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size); +} + +/* + * Test HIDIOCGRDESC ioctl to get report descriptor data + */ +TEST_F(hidraw, ioctl_rdesc) +{ + struct hidraw_report_descriptor desc; + int err; + + /* get the full report descriptor */ + desc.size =3D sizeof(rdesc); + err =3D ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed"); + + /* verify the descriptor data matches our test descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0) + TH_LOG("report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRDESC ioctl with smaller buffer size + */ +TEST_F(hidraw, ioctl_rdesc_small_buffer) +{ + struct hidraw_report_descriptor desc; + int err; + size_t small_size =3D sizeof(rdesc) / 2; /* request half the descriptor s= ize */ + + /* get partial report descriptor */ + desc.size =3D small_size; + err =3D ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer"); + + /* verify we got the first part of the descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0) + TH_LOG("partial report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRAWINFO ioctl to get device information + */ +TEST_F(hidraw, ioctl_rawinfo) +{ + struct hidraw_devinfo devinfo; + int err; + + /* get device info */ + err =3D ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed"); + + /* verify device info matches our test setup */ + ASSERT_EQ(devinfo.bustype, BUS_USB) + TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype); + ASSERT_EQ(devinfo.vendor, 0x0001) + TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor); + ASSERT_EQ(devinfo.product, 0x0a37) + TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product); +} + +/* + * Test HIDIOCGFEATURE ioctl to get feature report + */ +TEST_F(hidraw, ioctl_gfeature) +{ + __u8 buf[10] =3D {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] =3D 1; + + /* get feature report */ + err =3D ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed,= got %d", err); + + /* verify we got the expected feature data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] =3D %d, got %d", feature_data[0], buf[0= ]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] =3D %d, got %d", feature_data[1], buf[1= ]); +} + +/* + * Test HIDIOCGFEATURE ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_gfeature_invalid) +{ + __u8 buf[10] =3D {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] =3D 2; + + /* try to get feature report */ + err =3D ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid = report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSFEATURE ioctl to set feature report + */ +TEST_F(hidraw, ioctl_sfeature) +{ + __u8 buf[10] =3D {0}; + int err; + + /* prepare feature report data */ + buf[0] =3D 1; /* report ID */ + buf[1] =3D 0x42; + buf[2] =3D 0x24; + + /* set feature report */ + err =3D ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGINPUT ioctl to get input report + */ +TEST_F(hidraw, ioctl_ginput) +{ + __u8 buf[10] =3D {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] =3D 1; + + /* get input report */ + err =3D ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, g= ot %d", err); + + /* verify we got the expected input data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] =3D %d, got %d", feature_data[0], buf[0= ]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] =3D %d, got %d", feature_data[1], buf[1= ]); +} + +/* + * Test HIDIOCGINPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_ginput_invalid) +{ + __u8 buf[10] =3D {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] =3D 2; + + /* try to get input report */ + err =3D ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid re= port ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSINPUT ioctl to set input report + */ +TEST_F(hidraw, ioctl_sinput) +{ + __u8 buf[10] =3D {0}; + int err; + + /* prepare input report data */ + buf[0] =3D 1; /* report ID */ + buf[1] =3D 0x55; + buf[2] =3D 0xAA; + + /* set input report */ + err =3D ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGOUTPUT ioctl to get output report + */ +TEST_F(hidraw, ioctl_goutput) +{ + __u8 buf[10] =3D {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] =3D 1; + + /* get output report */ + err =3D ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, = got %d", err); + + /* verify we got the expected output data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] =3D %d, got %d", feature_data[0], buf[0= ]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] =3D %d, got %d", feature_data[1], buf[1= ]); +} + +/* + * Test HIDIOCGOUTPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_goutput_invalid) +{ + __u8 buf[10] =3D {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] =3D 2; + + /* try to get output report */ + err =3D ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid r= eport ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSOUTPUT ioctl to set output report + */ +TEST_F(hidraw, ioctl_soutput) +{ + __u8 buf[10] =3D {0}; + int err; + + /* prepare output report data */ + buf[0] =3D 1; /* report ID */ + buf[1] =3D 0x33; + buf[2] =3D 0xCC; + + /* set output report */ + err =3D ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGRAWNAME ioctl to get device name string + */ +TEST_F(hidraw, ioctl_rawname) +{ + char name[256] =3D {0}; + char expected_name[64]; + int err; + + /* get device name */ + err =3D ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err); + + /* construct expected name based on device id */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", sel= f->hid.dev_id); + + /* verify the name matches expected pattern */ + ASSERT_EQ(strcmp(name, expected_name), 0) + TH_LOG("expected name '%s', got '%s'", expected_name, name); +} + +/* + * Test HIDIOCGRAWPHYS ioctl to get device physical address string + */ +TEST_F(hidraw, ioctl_rawphys) +{ + char phys[256] =3D {0}; + char expected_phys[64]; + int err; + + /* get device physical address */ + err =3D ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err); + + /* construct expected phys based on device id */ + snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id); + + /* verify the phys matches expected value */ + ASSERT_EQ(strcmp(phys, expected_phys), 0) + TH_LOG("expected phys '%s', got '%s'", expected_phys, phys); +} + +/* + * Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string + */ +TEST_F(hidraw, ioctl_rawuniq) +{ + char uniq[256] =3D {0}; + int err; + + /* get device unique identifier */ + err =3D ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err); + + /* uniq is typically empty in our test setup */ + ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq); +} + +/* + * Test device string ioctls with small buffer sizes + */ +TEST_F(hidraw, ioctl_strings_small_buffer) +{ + char small_buf[8] =3D {0}; + char expected_name[64]; + int err; + + /* test HIDIOCGRAWNAME with small buffer */ + err =3D ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_b= uf); + ASSERT_EQ(err, sizeof(small_buf)) + TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err); + + /* construct expected truncated name */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", sel= f->hid.dev_id); + + /* verify we got truncated name (first 8 chars, no null terminator guaran= teed) */ + ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0) + TH_LOG("expected truncated name to match first %zu chars", sizeof(small_= buf)); + + /* Note: hidraw driver doesn't guarantee null termination when buffer is = too small */ +} + int main(int argc, char **argv) { return test_harness_run(argc, argv); --=20 2.51.0 From nobody Thu Oct 2 18:15:52 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9EE7430DD12; Fri, 12 Sep 2025 16:59:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757696340; cv=none; b=tNB96bj1o9lJWgV9APVwFhEOKxNsTDj57ndBb/4rl0CTqHrdksEVO7FskT2KdjKFoz6NFfCrL5SDdLqYxO/B+1koFYOi1WP62GPGx36Fhp4QG99gXV1NmehX9eLc2DPd6EhJhXR3HOpDnzo/yGdVxxIyr++KGlr4DQLr1N8lCt0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757696340; c=relaxed/simple; bh=IlMePipPTDazOklUtuC2COQIsM/h+CEoQ9yWVs4bDJw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AQj03S+1iIxZ47jO7RFAY42I9RnAlm5CxPtAvTT77EpMm/o0aPM3tMheTjDEY03NSmIKXvTMTUgfztlO6FHkWocGI+lZkgomhySCjQcO+K8AjVMhRT1cOnBye9ahejogst/LfI0N3SyMcutjXHI02Oci/hdp06oCxBEKh1x7FqU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Qgxi7q4K; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Qgxi7q4K" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 044CCC4CEF4; Fri, 12 Sep 2025 16:58:58 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757696340; bh=IlMePipPTDazOklUtuC2COQIsM/h+CEoQ9yWVs4bDJw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=Qgxi7q4KkB/q2N/2y9PwxPeIlOori9y8GnJJ/k7a6cI+hCXneMWRtojS+Oh5VyGAd 1JCc3vm7nRGI56hmfKykafVjBnO4Wc9Itxm6D5QCrPiz/c9PgFvie0/9K777rEUdO4 rWmIeZ8uiKyGTyqSh7OPzx+EYrkx7JUbov378C2xA1fjpR16v8ZW0J7AgexKMje7Sy 1H77LxfA7m2I9r6DsmadClZ0No+9QFZnJH1lVO/oYzLXmWCDDig55pK6trgQRYz98A w7H7WWbFIlWaaYPmJz3pXqngdbgsmd8cPB9OzOrC4dwgtz9mCf8/Gen5/8RsmIZudf dGNjXF3ynvD3Q== From: Benjamin Tissoires Date: Fri, 12 Sep 2025 18:58:50 +0200 Subject: [PATCH v3 2/3] selftests/hid: hidraw: forge wrong ioctls and tests them Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250912-b4-hidraw-ioctls-v3-2-cd2c6efd8c20@kernel.org> References: <20250912-b4-hidraw-ioctls-v3-0-cd2c6efd8c20@kernel.org> In-Reply-To: <20250912-b4-hidraw-ioctls-v3-0-cd2c6efd8c20@kernel.org> To: Jiri Kosina , Shuah Khan , Arnd Bergmann Cc: linux-input@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Benjamin Tissoires X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1757696335; l=5515; i=bentiss@kernel.org; s=20230215; h=from:subject:message-id; bh=IlMePipPTDazOklUtuC2COQIsM/h+CEoQ9yWVs4bDJw=; b=qsNqc+31neoyvlUgtAOtVIEq3T3gfaRnwgKXxxJcAoWtUo6FTE//fGjYaHY8RERg/o1U48XIH CdwOLBg2ZD3CJK5bau8OXWnMbf40h2ZAGBBkSKerBkCH3YhrQet5FTy X-Developer-Key: i=bentiss@kernel.org; a=ed25519; pk=7D1DyAVh6ajCkuUTudt/chMuXWIJHlv2qCsRkIizvFw= We also need coverage for when the malicious user is not using the proper ioctls definitions and tries to work around the driver. Most of the scaffholding has been generated by claude-4-sonnet and then carefully reviewed. Suggested-by: Arnd Bergmann Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/hid/hidraw.c | 127 +++++++++++++++++++++++++++++++= ++++ 1 file changed, 127 insertions(+) diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests= /hid/hidraw.c index 6d61d03e2ef05e1900fe5a3938d93421717b2621..d625772f8b7cf71fd94956d3a49= d54ff44e2b34d 100644 --- a/tools/testing/selftests/hid/hidraw.c +++ b/tools/testing/selftests/hid/hidraw.c @@ -332,6 +332,133 @@ TEST_F(hidraw, ioctl_gfeature_invalid) ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); } =20 +/* + * Test ioctl with incorrect nr bits + */ +TEST_F(hidraw, ioctl_invalid_nr) +{ + char buf[256] =3D {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd =3D _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is n= ot valid */ + + /* test the ioctl */ + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should = have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d",= errno); + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd =3D _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should h= ave failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", = errno); + + /* also test with bigger number */ + bad_cmd =3D _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid= as well */ + + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) shoul= d have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d= ", errno); + + /* also test with bigger number: 0x42 is not valid as well */ + bad_cmd =3D _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf)); + + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) shou= ld have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %= d", errno); +} + +/* + * Test ioctl with incorrect type bits + */ +TEST_F(hidraw, ioctl_invalid_type) +{ + char buf[256] =3D {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_TYPE bits + */ + bad_cmd =3D _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' sh= ould be 'H' */ + + /* test the ioctl */ + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have fail= ed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got e= rrno %d", errno); +} + +/* + * Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_gfeature_invalid_dir) +{ + __u8 buf[10] =3D {0}; + int err; + unsigned int bad_cmd; + + /* set report ID 1 in first byte */ + buf[0] =3D 1; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_W= RITE + */ + bad_cmd =3D _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_W= RITE|_IOC_READ */ + + /* try to get feature report with wrong direction bits */ + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have = failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got = errno %d", errno); + + /* also test with only _IOC_READ */ + bad_cmd =3D _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WR= ITE|_IOC_READ */ + + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have = failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got = errno %d", errno); +} + +/* + * Test read-only ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_readonly_invalid_dir) +{ + char buf[256] =3D {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE + */ + bad_cmd =3D _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_R= EAD */ + + /* try to get device name with wrong direction bits */ + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have = failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got = errno %d", errno); + + /* also test with _IOC_WRITE|_IOC_READ */ + bad_cmd =3D _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should= be only _IOC_READ */ + + err =3D ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have = failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got = errno %d", errno); +} + /* * Test HIDIOCSFEATURE ioctl to set feature report */ --=20 2.51.0 From nobody Thu Oct 2 18:15:52 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id ED615329F0B; Fri, 12 Sep 2025 16:59:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757696343; cv=none; b=jp3Me+29Q2w+M7Gd8Ss5EXSaKN1gfLxxHB3XliY9iz1f2rxW7+rjY2UDZr0YZ/wkajrY2PshPTORwXzGIAdynhhGPK96kv7gdG83fd98+WUZ2PAM+yTyEph98KHAri9DJj3QfomRbSOUhUH5bVu+9cb8bNStZaRnR2WS7EyZgsI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757696343; c=relaxed/simple; bh=mv+W9p3Tv6+0MLm2i3qDPz7HbebqWW25ckjAaTa8EKU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=m5CLdyZYoOPNgitWfPWXXM8xwGKis0+A37b7RaIWbGUWRXFk66h8lLvSTgNPTvrb1k83sK6n1AP/X5Aj5oqF04upsKY0xw+Huj/LfnyWzvMP+1cONxFvvKp0/JKWhGpVeoZiMdDJBT+kVDI/rrJcDTCOasT+xsoxWiMxlkfJ8v0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=P7j2rEJg; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="P7j2rEJg" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E5757C4CEF1; Fri, 12 Sep 2025 16:59:00 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1757696342; bh=mv+W9p3Tv6+0MLm2i3qDPz7HbebqWW25ckjAaTa8EKU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:From; b=P7j2rEJgIGknIyu/8bxWcSasQlKt0MyukQ84xlpTG624a+r6eqA5agmHbLTe4ib0p njc7NCyxdoDNJyeIyv2TKyYLtFyhD+udacOIYxQv3nuQc5M6vUIH83LDFcfsE+iEEa JjTI2+Ns8R1qdpvONP+4r06J8ms9NqTwnJxuKIm6bO+PCIJF40dpVKDrtGCphZ8g8W GI88PdmhJMcwLnM4qbGQDTzKc3BVD7hnVAu5CKiY62QEQ9CastgMhD99bVNONodlYQ rOHIo7mejxwWp67kq2EIiB3ea9wCs6A6QWags/P3KT22JTanH5tZ+UiHgSnLWYOk9S gIRSNs/a/71hw== From: Benjamin Tissoires Date: Fri, 12 Sep 2025 18:58:51 +0200 Subject: [PATCH v3 3/3] HID: hidraw: tighten ioctl command parsing Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20250912-b4-hidraw-ioctls-v3-3-cd2c6efd8c20@kernel.org> References: <20250912-b4-hidraw-ioctls-v3-0-cd2c6efd8c20@kernel.org> In-Reply-To: <20250912-b4-hidraw-ioctls-v3-0-cd2c6efd8c20@kernel.org> To: Jiri Kosina , Shuah Khan , Arnd Bergmann Cc: linux-input@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, Benjamin Tissoires , Arnd Bergmann X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1757696335; l=9010; i=bentiss@kernel.org; s=20230215; h=from:subject:message-id; bh=mv+W9p3Tv6+0MLm2i3qDPz7HbebqWW25ckjAaTa8EKU=; b=0mn5I+O6EUYxaJddRDFCF2SmhTfbyGnhug/hUk+jZJjaAZudsKFk/ZDpT6wyBhZS6V2fzpu0j wL0McHPPFO+C9A2BBVV/IUvvMbFWr3ZYRCjH8CeFQl1VND1pRm/RMGH X-Developer-Key: i=bentiss@kernel.org; a=ed25519; pk=7D1DyAVh6ajCkuUTudt/chMuXWIJHlv2qCsRkIizvFw= The handling for variable-length ioctl commands in hidraw_ioctl() is rather complex and the check for the data direction is incomplete. Simplify this code by factoring out the various ioctls grouped by dir and size, and using a switch() statement with the size masked out, to ensure the rest of the command is correctly matched. Fixes: 9188e79ec3fd ("HID: add phys and name ioctls to hidraw") Reported-by: Arnd Bergmann Signed-off-by: Benjamin Tissoires --- drivers/hid/hidraw.c | 224 ++++++++++++++++++++++++----------------= ---- include/uapi/linux/hidraw.h | 2 + 2 files changed, 124 insertions(+), 102 deletions(-) diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index c887f48756f4be2a4bac03128f2885bde96c1e39..bbd6f23bce78951c7d667ff5c1c= 923cee3509e3f 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -394,27 +394,15 @@ static int hidraw_revoke(struct hidraw_list *list) return 0; } =20 -static long hidraw_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev,= unsigned int cmd, + void __user *arg) { - struct inode *inode =3D file_inode(file); - unsigned int minor =3D iminor(inode); - long ret =3D 0; - struct hidraw *dev; - struct hidraw_list *list =3D file->private_data; - void __user *user_arg =3D (void __user*) arg; - - down_read(&minors_rwsem); - dev =3D hidraw_table[minor]; - if (!dev || !dev->exist || hidraw_is_revoked(list)) { - ret =3D -ENODEV; - goto out; - } + struct hid_device *hid =3D dev->hid; =20 switch (cmd) { case HIDIOCGRDESCSIZE: - if (put_user(dev->hid->rsize, (int __user *)arg)) - ret =3D -EFAULT; + if (put_user(hid->rsize, (int __user *)arg)) + return -EFAULT; break; =20 case HIDIOCGRDESC: @@ -422,113 +410,145 @@ static long hidraw_ioctl(struct file *file, unsigne= d int cmd, __u32 len; =20 if (get_user(len, (int __user *)arg)) - ret =3D -EFAULT; - else if (len > HID_MAX_DESCRIPTOR_SIZE - 1) - ret =3D -EINVAL; - else if (copy_to_user(user_arg + offsetof( - struct hidraw_report_descriptor, - value[0]), - dev->hid->rdesc, - min(dev->hid->rsize, len))) - ret =3D -EFAULT; + return -EFAULT; + + if (len > HID_MAX_DESCRIPTOR_SIZE - 1) + return -EINVAL; + + if (copy_to_user(arg + offsetof( + struct hidraw_report_descriptor, + value[0]), + hid->rdesc, + min(hid->rsize, len))) + return -EFAULT; + break; } case HIDIOCGRAWINFO: { struct hidraw_devinfo dinfo; =20 - dinfo.bustype =3D dev->hid->bus; - dinfo.vendor =3D dev->hid->vendor; - dinfo.product =3D dev->hid->product; - if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) - ret =3D -EFAULT; + dinfo.bustype =3D hid->bus; + dinfo.vendor =3D hid->vendor; + dinfo.product =3D hid->product; + if (copy_to_user(arg, &dinfo, sizeof(dinfo))) + return -EFAULT; break; } case HIDIOCREVOKE: { - if (user_arg) - ret =3D -EINVAL; - else - ret =3D hidraw_revoke(list); - break; + struct hidraw_list *list =3D file->private_data; + + if (arg) + return -EINVAL; + + return hidraw_revoke(list); } default: - { - struct hid_device *hid =3D dev->hid; - if (_IOC_TYPE(cmd) !=3D 'H') { - ret =3D -EINVAL; - break; - } + /* + * None of the above ioctls can return -EAGAIN, so + * use it as a marker that we need to check variable + * length ioctls. + */ + return -EAGAIN; + } =20 - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCSFEATURE(0))) { - int len =3D _IOC_SIZE(cmd); - ret =3D hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); - break; - } - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCGFEATURE(0))) { - int len =3D _IOC_SIZE(cmd); - ret =3D hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); - break; - } + return 0; +} =20 - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCSINPUT(0))) { - int len =3D _IOC_SIZE(cmd); - ret =3D hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); - break; - } - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCGINPUT(0))) { - int len =3D _IOC_SIZE(cmd); - ret =3D hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); - break; - } +static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw= *dev, unsigned int cmd, + void __user *user_arg) +{ + int len =3D _IOC_SIZE(cmd); + + switch (cmd & ~IOCSIZE_MASK) { + case HIDIOCSFEATURE(0): + return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); + case HIDIOCGFEATURE(0): + return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); + case HIDIOCSINPUT(0): + return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); + case HIDIOCGINPUT(0): + return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); + case HIDIOCSOUTPUT(0): + return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); + case HIDIOCGOUTPUT(0): + return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); + } =20 - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCSOUTPUT(0))) { - int len =3D _IOC_SIZE(cmd); - ret =3D hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); - break; - } - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCGOUTPUT(0))) { - int len =3D _IOC_SIZE(cmd); - ret =3D hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); - break; - } + return -EINVAL; +} =20 - /* Begin Read-only ioctls. */ - if (_IOC_DIR(cmd) !=3D _IOC_READ) { - ret =3D -EINVAL; - break; - } +static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw= *dev, unsigned int cmd, + void __user *user_arg) +{ + struct hid_device *hid =3D dev->hid; + int len =3D _IOC_SIZE(cmd); + int field_len; + + switch (cmd & ~IOCSIZE_MASK) { + case HIDIOCGRAWNAME(0): + field_len =3D strlen(hid->name) + 1; + if (len > field_len) + len =3D field_len; + return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; + case HIDIOCGRAWPHYS(0): + field_len =3D strlen(hid->phys) + 1; + if (len > field_len) + len =3D field_len; + return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; + case HIDIOCGRAWUNIQ(0): + field_len =3D strlen(hid->uniq) + 1; + if (len > field_len) + len =3D field_len; + return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len; + } =20 - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCGRAWNAME(0))) { - int len =3D strlen(hid->name) + 1; - if (len > _IOC_SIZE(cmd)) - len =3D _IOC_SIZE(cmd); - ret =3D copy_to_user(user_arg, hid->name, len) ? - -EFAULT : len; - break; - } + return -EINVAL; +} =20 - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCGRAWPHYS(0))) { - int len =3D strlen(hid->phys) + 1; - if (len > _IOC_SIZE(cmd)) - len =3D _IOC_SIZE(cmd); - ret =3D copy_to_user(user_arg, hid->phys, len) ? - -EFAULT : len; - break; - } +static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned lon= g arg) +{ + struct inode *inode =3D file_inode(file); + unsigned int minor =3D iminor(inode); + struct hidraw *dev; + struct hidraw_list *list =3D file->private_data; + void __user *user_arg =3D (void __user *)arg; + int ret; =20 - if (_IOC_NR(cmd) =3D=3D _IOC_NR(HIDIOCGRAWUNIQ(0))) { - int len =3D strlen(hid->uniq) + 1; - if (len > _IOC_SIZE(cmd)) - len =3D _IOC_SIZE(cmd); - ret =3D copy_to_user(user_arg, hid->uniq, len) ? - -EFAULT : len; - break; - } - } + down_read(&minors_rwsem); + dev =3D hidraw_table[minor]; + if (!dev || !dev->exist || hidraw_is_revoked(list)) { + ret =3D -ENODEV; + goto out; + } + + if (_IOC_TYPE(cmd) !=3D 'H') { + ret =3D -EINVAL; + goto out; + } =20 + if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) =3D=3D 0) { ret =3D -ENOTTY; + goto out; } + + ret =3D hidraw_fixed_size_ioctl(file, dev, cmd, user_arg); + if (ret !=3D -EAGAIN) + goto out; + + switch (_IOC_DIR(cmd)) { + case (_IOC_READ | _IOC_WRITE): + ret =3D hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg); + break; + case _IOC_READ: + ret =3D hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg); + break; + default: + /* Any other IOC_DIR is wrong */ + ret =3D -EINVAL; + } + out: up_read(&minors_rwsem); return ret; diff --git a/include/uapi/linux/hidraw.h b/include/uapi/linux/hidraw.h index d5ee269864e07fcaba481fa285bacbd98739e44f..ebd701b3c18d9d7465880199091= 933f13f2e1128 100644 --- a/include/uapi/linux/hidraw.h +++ b/include/uapi/linux/hidraw.h @@ -48,6 +48,8 @@ struct hidraw_devinfo { #define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len) #define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */ =20 +#define HIDIOCTL_LAST _IOC_NR(HIDIOCREVOKE) + #define HIDRAW_FIRST_MINOR 0 #define HIDRAW_MAX_DEVICES 64 /* number of reports to buffer */ --=20 2.51.0