diff --git a/doc/run.md b/doc/run.md index 195b52399..c2e143a65 100644 --- a/doc/run.md +++ b/doc/run.md @@ -2,6 +2,10 @@ The DPDK based PMD requires VFIO (IOMMU) and huge pages to run, but it also supports non-root run, making it easy to deploy within Docker/Kubernetes environments. +## Network Security + +Deploy RTP/UDP receivers on trusted media networks and restrict ingress with VLANs or firewall rules. MTL validates received packet fields, but network isolation limits exposure to malformed or unauthorized traffic. + ## 1. IOMMU Setup ### 1.1. [IOMMU Setup](chunks/_iommu_setup.md) diff --git a/lib/src/st2110/st_rx_video_session.c b/lib/src/st2110/st_rx_video_session.c index b5a0c1875..89d56daf9 100644 --- a/lib/src/st2110/st_rx_video_session.c +++ b/lib/src/st2110/st_rx_video_session.c @@ -1679,6 +1679,15 @@ static int rv_handle_frame_pkt(struct st_rx_video_session_impl* s, struct rte_mb s->port_user_stats.stat_pkts_offset_dropped++; return -EIO; } + if (extra_rtp && s->st20_linesize > s->st20_bytes_in_line) { + size_t continuation_row = (size_t)line1_number + 1; + size_t continuation_length = payload_length - line1_length; + if ((continuation_row >= ops->height) || + (continuation_length > s->st20_fb_size - continuation_row * s->st20_linesize)) { + s->port_user_stats.stat_pkts_offset_dropped++; + return -EIO; + } + } /* check if valid pkt len */ size_t pkt_payload_len = mbuf->pkt_len - sizeof(struct st_rfc4175_video_hdr); diff --git a/tests/unit/README.md b/tests/unit/README.md index 81cae7149..cf37877a9 100644 --- a/tests/unit/README.md +++ b/tests/unit/README.md @@ -76,6 +76,29 @@ Filter to a single suite or test: ./build_unit/tests/unit/UnitTest --gtest_list_tests ``` +### ST20 continuation bounds regression + +`St20RxHeaderValidationTest.FinalRowContinuationPastFrameDropped` feeds the +production RFC 4175 frame handler a continuation SRD whose second row begins +past its frame buffer. It needs no NIC, root, hugepages, or packet injector. + +```bash +meson setup build_unit -Denable_unit_tests=true +ninja -C build_unit tests/unit/UnitTest +./build_unit/tests/unit/UnitTest \ + --gtest_filter='St20RxHeaderValidationTest.FinalRowContinuationPastFrameDropped' +``` + +The test geometry is two 40-byte rows with a padded 48-byte stride, making an +80-byte frame. It sends row 1 with a 12-byte first SRD and a 12-byte +continuation. The original offset check accepts the first copy, while the +second copy would begin at byte 96. Correct behavior is a negative return and +one `stat_pkts_offset_dropped` increment. Before the continuation bound check, +this test fails with `rc` equal to `0` and the drop counter equal to `0`. + +`St20RxHeaderValidationTest.PaddedLineContinuationAccepted` is the companion +positive test. It verifies a valid padded continuation remains accepted. + > **Why `LD_PRELOAD=libasan.so`?** `libmtl.so` is built with AddressSanitizer > when `enable_unit_tests=true`. Preloading the runtime first prevents > init-order interposition issues between gtest, libstdc++, and libasan. diff --git a/tests/unit/session/st20/header_validation_test.cpp b/tests/unit/session/st20/header_validation_test.cpp index 0a617c037..d713b068d 100644 --- a/tests/unit/session/st20/header_validation_test.cpp +++ b/tests/unit/session/st20/header_validation_test.cpp @@ -73,3 +73,21 @@ TEST_F(St20RxHeaderValidationTest, WrongInterlaceDropped) { EXPECT_EQ(rc, -EINVAL); EXPECT_EQ(wrong_interlace(), 1u); } + +TEST_F(St20RxHeaderValidationTest, FinalRowContinuationPastFrameDropped) { + ut20_ctx_set_linesize(ctx_, 48); + + int rc = ut20_feed_continuation_pkt(ctx_, 0, 1000, 1, 0, 12, 12, MTL_SESSION_PORT_P); + + EXPECT_LT(rc, 0); + EXPECT_EQ(ut20_stat_offset_dropped(ctx_), 1u); +} + +TEST_F(St20RxHeaderValidationTest, PaddedLineContinuationAccepted) { + ut20_ctx_set_linesize(ctx_, 48); + + int rc = ut20_feed_continuation_pkt(ctx_, 0, 1000, 0, 0, 12, 12, MTL_SESSION_PORT_P); + + EXPECT_EQ(rc, 0); + EXPECT_EQ(ut20_stat_offset_dropped(ctx_), 0u); +} diff --git a/tests/unit/session/st20_harness.c b/tests/unit/session/st20_harness.c index f1fb6e0f2..48e1b748a 100644 --- a/tests/unit/session/st20_harness.c +++ b/tests/unit/session/st20_harness.c @@ -251,6 +251,46 @@ static struct rte_mbuf* make_video_mbuf(uint32_t seq, uint32_t ts, uint16_t line return make_video_mbuf_full(seq, ts, line_num, line_offset, line_length, 0, 0); } +static struct rte_mbuf* make_video_continuation_mbuf(uint32_t seq, uint32_t ts, + uint16_t line_num, + uint16_t line_offset, + uint16_t line1_length, + uint16_t line2_length) { + struct rte_mbuf* m = rte_pktmbuf_alloc(ut_pool()); + if (!m) return NULL; + + size_t total = sizeof(struct st_rfc4175_video_hdr) + + sizeof(struct st20_rfc4175_extra_rtp_hdr) + line1_length + line2_length; + if (rte_pktmbuf_tailroom(m) < total) { + rte_pktmbuf_free(m); + return NULL; + } + + uint8_t* buf = rte_pktmbuf_mtod(m, uint8_t*); + memset(buf, 0, total); + size_t hdr_offset = + sizeof(struct st_rfc4175_video_hdr) - sizeof(struct st20_rfc4175_rtp_hdr); + struct st20_rfc4175_rtp_hdr* rtp = (struct st20_rfc4175_rtp_hdr*)(buf + hdr_offset); + struct st20_rfc4175_extra_rtp_hdr* extra_rtp = + (struct st20_rfc4175_extra_rtp_hdr*)&rtp[1]; + + rtp->base.version = 2; + rtp->base.seq_number = htons((uint16_t)(seq & 0xFFFF)); + rtp->base.tmstamp = htonl(ts); + rtp->seq_number_ext = htons((uint16_t)(seq >> 16)); + rtp->row_number = htons(line_num); + rtp->row_offset = htons(line_offset | ST20_SRD_OFFSET_CONTINUATION); + rtp->row_length = htons(line1_length); + extra_rtp->row_length = htons(line2_length); + extra_rtp->row_offset = 0; + extra_rtp->row_number = htons(line_num + 1); + + m->data_len = total; + m->pkt_len = total; + m->next = NULL; + return m; +} + /* ── pkt_idx → line/offset mapping ────────────────────────────────────── */ static void pkt_idx_to_line(int pkt_idx, uint16_t* line_num, uint16_t* line_offset, @@ -272,6 +312,18 @@ int ut20_feed_pkt(ut20_test_ctx* ctx, uint32_t seq, uint32_t ts, uint16_t line_n return rc; } +int ut20_feed_continuation_pkt(ut20_test_ctx* ctx, uint32_t seq, uint32_t ts, + uint16_t line_num, uint16_t line_offset, + uint16_t line1_length, uint16_t line2_length, + enum mtl_session_port port) { + struct rte_mbuf* m = make_video_continuation_mbuf(seq, ts, line_num, line_offset, + line1_length, line2_length); + if (!m) return -1; + int rc = rv_handle_frame_pkt(&ctx->session, m, port, true); + rte_pktmbuf_free(m); + return rc; +} + int ut20_feed_frame_pkt(ut20_test_ctx* ctx, int pkt_idx, uint32_t ts, enum mtl_session_port port) { uint32_t seq = ts * (uint32_t)ctx->session.ops.height + (uint32_t)pkt_idx; @@ -393,6 +445,12 @@ void ut20_ctx_set_ssrc(ut20_test_ctx* ctx, uint32_t ssrc) { ctx->session.ops.ssrc = ssrc; } +void ut20_ctx_set_linesize(ut20_test_ctx* ctx, uint32_t linesize) { + ctx->session.ops.linesize = linesize; + ctx->session.st20_linesize = linesize; + ctx->session.st20_fb_size = linesize * ctx->session.ops.height; +} + void ut20_set_port_down(ut20_test_ctx* ctx, enum mtl_session_port port, bool down) { enum mtl_port phy = mt_port_logic2phy(ctx->session.port_maps, port); if (down) diff --git a/tests/unit/session/st20_harness.h b/tests/unit/session/st20_harness.h index b1c8e5473..db42ebe7a 100644 --- a/tests/unit/session/st20_harness.h +++ b/tests/unit/session/st20_harness.h @@ -62,6 +62,11 @@ void ut20_ctx_destroy(ut20_test_ctx* ctx); int ut20_feed_pkt(ut20_test_ctx* ctx, uint32_t seq, uint32_t ts, uint16_t line_num, uint16_t line_offset, uint16_t line_length, enum mtl_session_port port); +int ut20_feed_continuation_pkt(ut20_test_ctx* ctx, uint32_t seq, uint32_t ts, + uint16_t line_num, uint16_t line_offset, + uint16_t line1_length, uint16_t line2_length, + enum mtl_session_port port); + /* Convenience: feed packet `pkt_idx` (0 .. UT20_PKTS_PER_FRAME-1) of a * frame at timestamp `ts`. Auto-derives line_num/offset/length and uses * `pkt_idx` as the RTP sequence number (no per-port sequence tracking). */ @@ -109,6 +114,7 @@ int ut20_feed_pkt_ssrc(ut20_test_ctx* ctx, uint32_t seq, uint32_t ts, uint16_t l * ut20_ctx_create() and before feeding any packet. */ void ut20_ctx_set_pt(ut20_test_ctx* ctx, uint8_t pt); void ut20_ctx_set_ssrc(ut20_test_ctx* ctx, uint32_t ssrc); +void ut20_ctx_set_linesize(ut20_test_ctx* ctx, uint32_t linesize); /* Force a session port's physical link up/down. A link-down port is a dead * wire: it never receives data and is not charged per-port loss at frame