Repository navigation
fix(rnn): bounds check seq_len_max in BlockLSTM ops to prevent OOB access - #125400
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces validation checks in BlockLSTMOp and BlockLSTMGradOp within tensorflow/core/kernels/rnn/lstm_ops.cc to ensure that seq_len_max is non-negative and does not exceed timelen. There are no review comments provided, so I have no feedback to evaluate.
b3fd4a8 to
dbb38dc
Compare
dbb38dc to
86d7965
Compare
dmiltr3
left a comment
There was a problem hiding this comment.
Please add unit tests covering seq_len_max bounds validation for both BlockLSTM and BlockLSTMGrad in tensorflow/python/kernel_tests/nn_ops/rnn_cell_test.py to ensure invalid bounds (seq_len_max < 0 and seq_len_max > timelen) raise InvalidArgumentError and valid boundary values (0, timelen) execute as expected.
Suggested tests to add to rnn_cell_test.py:
@test_util.run_in_graph_and_eager_modes
def testBlockLSTMSeqLenMaxBounds(self):
timelen, batch_size, input_size, cell_size = 4, 2, 3, 5
x = constant_op.constant(0.1, shape=[timelen, batch_size, input_size], dtype=dtypes.float32)
cs_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
h_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
w = constant_op.constant(0.1, shape=[input_size + cell_size, 4 * cell_size], dtype=dtypes.float32)
wci = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wcf = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wco = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
b = constant_op.constant(0.1, shape=[4 * cell_size], dtype=dtypes.float32)
# Valid boundary cases: seq_len_max == 0 and seq_len_max == timelen
for valid_seq_len_max in [0, 2, timelen]:
res = self.evaluate(
gen_rnn_ops.BlockLSTM(
seq_len_max=constant_op.constant(valid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
forget_bias=0.0,
cell_clip=-1.0,
use_peephole=False,
)
)
self.assertEqual(len(res), 7)
# Invalid cases: seq_len_max < 0 or seq_len_max > timelen
for invalid_seq_len_max in [-1, timelen + 1, 100]:
with self.assertRaisesRegex(
(ValueError, errors_impl.InvalidArgumentError),
r"seq_len_max must be between 0 and",
):
self.evaluate(
gen_rnn_ops.BlockLSTM(
seq_len_max=constant_op.constant(invalid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
forget_bias=0.0,
cell_clip=-1.0,
use_peephole=False,
)
)
@test_util.run_in_graph_and_eager_modes
def testBlockLSTMGradSeqLenMaxBounds(self):
timelen, batch_size, input_size, cell_size = 4, 2, 3, 5
x = constant_op.constant(0.1, shape=[timelen, batch_size, input_size], dtype=dtypes.float32)
cs_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
h_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
w = constant_op.constant(0.1, shape=[input_size + cell_size, 4 * cell_size], dtype=dtypes.float32)
wci = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wcf = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wco = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
b = constant_op.constant(0.1, shape=[4 * cell_size], dtype=dtypes.float32)
i = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
cs = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
f = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
o = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
ci = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
co = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
h = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
cs_grad = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
h_grad = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
# Valid boundary cases: seq_len_max == 0 and seq_len_max == timelen
for valid_seq_len_max in [0, 2, timelen]:
res = self.evaluate(
gen_rnn_ops.BlockLSTMGrad(
seq_len_max=constant_op.constant(valid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
i=i,
cs=cs,
f=f,
o=o,
ci=ci,
co=co,
h=h,
cs_grad=cs_grad,
h_grad=h_grad,
use_peephole=False,
)
)
self.assertEqual(len(res), 8)
# Invalid cases: seq_len_max < 0 or seq_len_max > timelen
for invalid_seq_len_max in [-1, timelen + 1, 100]:
with self.assertRaisesRegex(
(ValueError, errors_impl.InvalidArgumentError),
r"seq_len_max must be between 0 and",
):
self.evaluate(
gen_rnn_ops.BlockLSTMGrad(
seq_len_max=constant_op.constant(invalid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
i=i,
cs=cs,
f=f,
o=o,
ci=ci,
co=co,
h=h,
cs_grad=cs_grad,
h_grad=h_grad,
use_peephole=False,
)
)|
Hi team! Just a gentle ping on this PR. Let me know if there's anything else you need from my side or if any changes are required. Thanks! |
dmiltr3
left a comment
There was a problem hiding this comment.
Please add unit tests covering seq_len_max bounds validation for both BlockLSTM and BlockLSTMGrad in tensorflow/python/kernel_tests/nn_ops/rnn_cell_test.py to ensure invalid bounds (seq_len_max < 0 and seq_len_max > timelen) raise InvalidArgumentError and valid boundary values (0, timelen) execute as expected.
Suggested tests to add to rnn_cell_test.py:
@test_util.run_in_graph_and_eager_modes
def testBlockLSTMSeqLenMaxBounds(self):
timelen, batch_size, input_size, cell_size = 4, 2, 3, 5
x = constant_op.constant(0.1, shape=[timelen, batch_size, input_size], dtype=dtypes.float32)
cs_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
h_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
w = constant_op.constant(0.1, shape=[input_size + cell_size, 4 * cell_size], dtype=dtypes.float32)
wci = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wcf = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wco = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
b = constant_op.constant(0.1, shape=[4 * cell_size], dtype=dtypes.float32)
# Valid boundary cases: seq_len_max == 0 and seq_len_max == timelen
for valid_seq_len_max in [0, 2, timelen]:
res = self.evaluate(
gen_rnn_ops.BlockLSTM(
seq_len_max=constant_op.constant(valid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
forget_bias=0.0,
cell_clip=-1.0,
use_peephole=False,
)
)
self.assertEqual(len(res), 7)
# Invalid cases: seq_len_max < 0 or seq_len_max > timelen
for invalid_seq_len_max in [-1, timelen + 1, 100]:
with self.assertRaisesRegex(
(ValueError, errors_impl.InvalidArgumentError),
r"seq_len_max must be between 0 and",
):
self.evaluate(
gen_rnn_ops.BlockLSTM(
seq_len_max=constant_op.constant(invalid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
forget_bias=0.0,
cell_clip=-1.0,
use_peephole=False,
)
)
@test_util.run_in_graph_and_eager_modes
def testBlockLSTMGradSeqLenMaxBounds(self):
timelen, batch_size, input_size, cell_size = 4, 2, 3, 5
x = constant_op.constant(0.1, shape=[timelen, batch_size, input_size], dtype=dtypes.float32)
cs_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
h_prev = constant_op.constant(0.1, shape=[batch_size, cell_size], dtype=dtypes.float32)
w = constant_op.constant(0.1, shape=[input_size + cell_size, 4 * cell_size], dtype=dtypes.float32)
wci = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wcf = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
wco = constant_op.constant(0.1, shape=[cell_size], dtype=dtypes.float32)
b = constant_op.constant(0.1, shape=[4 * cell_size], dtype=dtypes.float32)
i = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
cs = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
f = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
o = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
ci = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
co = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
h = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
cs_grad = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
h_grad = constant_op.constant(0.1, shape=[timelen, batch_size, cell_size], dtype=dtypes.float32)
# Valid boundary cases: seq_len_max == 0 and seq_len_max == timelen
for valid_seq_len_max in [0, 2, timelen]:
res = self.evaluate(
gen_rnn_ops.BlockLSTMGrad(
seq_len_max=constant_op.constant(valid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
i=i,
cs=cs,
f=f,
o=o,
ci=ci,
co=co,
h=h,
cs_grad=cs_grad,
h_grad=h_grad,
use_peephole=False,
)
)
self.assertEqual(len(res), 8)
# Invalid cases: seq_len_max < 0 or seq_len_max > timelen
for invalid_seq_len_max in [-1, timelen + 1, 100]:
with self.assertRaisesRegex(
(ValueError, errors_impl.InvalidArgumentError),
r"seq_len_max must be between 0 and",
):
self.evaluate(
gen_rnn_ops.BlockLSTMGrad(
seq_len_max=constant_op.constant(invalid_seq_len_max, dtype=dtypes.int64),
x=x,
cs_prev=cs_prev,
h_prev=h_prev,
w=w,
wci=wci,
wcf=wcf,
wco=wco,
b=b,
i=i,
cs=cs,
f=f,
o=o,
ci=ci,
co=co,
h=h,
cs_grad=cs_grad,
h_grad=h_grad,
use_peephole=False,
)
)|
Hi @dmiltr3, I have added the requested unit tests covering |
dmiltr3
left a comment
There was a problem hiding this comment.
CI Failure Investigation & Required Test Fix
The GitHub CI checks failed across multiple platforms:
Root Cause
In tensorflow/core/kernels/rnn/lstm_ops.cc, consolidating the bounds check error message into "seq_len_max must be between 0 and ..." caused 4 existing unit tests in tensorflow/python/ops/rnn_grad_test.py to fail (testBlockLSTMSeqLenMaxNegative, testBlockLSTMSeqLenMaxTooLarge, testBlockLSTMGradSeqLenMaxNegative, testBlockLSTMGradSeqLenMaxTooLarge) because their assertion regex expects "seq_len_max must be <= timelen" or "seq_len_max must be >= 0".
Proposed Fix
Please update the expected error message regex in tensorflow/python/ops/rnn_grad_test.py:
diff --git a/tensorflow/python/ops/rnn_grad_test.py b/tensorflow/python/ops/rnn_grad_test.py
index a5d3e09..b61c94d 100644
--- a/tensorflow/python/ops/rnn_grad_test.py
+++ b/tensorflow/python/ops/rnn_grad_test.py
@@ -120,7 +120,7 @@ class RNNGradTest(test.TestCase):
def testBlockLSTMSeqLenMaxTooLarge(self):
w, b, x, cs_prev, h_prev, w_peephole = self._block_lstm_inputs()
with self.assertRaisesRegex(
- errors_impl.InvalidArgumentError, "seq_len_max must be <= timelen"
+ errors_impl.InvalidArgumentError, r"seq_len_max must be between 0 and"
):
self.evaluate(
self._block_lstm(w, b, x, cs_prev, h_prev, w_peephole, seq_len_max=10)
@@ -128,7 +128,7 @@ class RNNGradTest(test.TestCase):
def testBlockLSTMSeqLenMaxNegative(self):
w, b, x, cs_prev, h_prev, w_peephole = self._block_lstm_inputs()
with self.assertRaisesRegex(
- errors_impl.InvalidArgumentError, "seq_len_max must be >= 0"
+ errors_impl.InvalidArgumentError, r"seq_len_max must be between 0 and"
):
self.evaluate(
self._block_lstm(w, b, x, cs_prev, h_prev, w_peephole, seq_len_max=-1)
@@ -138,7 +138,7 @@ class RNNGradTest(test.TestCase):
def testBlockLSTMGradSeqLenMaxTooLarge(self):
with self.assertRaisesRegex(
- errors_impl.InvalidArgumentError, "seq_len_max must be <= timelen"
+ errors_impl.InvalidArgumentError, r"seq_len_max must be between 0 and"
):
self.evaluate(self._block_lstm_grad(seq_len_max=10))
@@ -142,7 +142,7 @@ class RNNGradTest(test.TestCase):
def testBlockLSTMGradSeqLenMaxNegative(self):
with self.assertRaisesRegex(
- errors_impl.InvalidArgumentError, "seq_len_max must be >= 0"
+ errors_impl.InvalidArgumentError, r"seq_len_max must be between 0 and"
):
self.evaluate(self._block_lstm_grad(seq_len_max=-1))|
Hi @dmiltr3, thanks for investigating the CI failure! I have updated the expected error message regex in |
dmiltr3
left a comment
There was a problem hiding this comment.
All tests and verification checks have passed. Changes look good.
|
I've applied the requested test fix for the regex. Could you please take another look and resolve the block if everything looks good? Also, I noticed some CI failures ( |
|
Hi @veerjain-1, this PR is waiting for internal approvals. We are working on it. Thank you! |
786097d
into
tensorflow:master
Resolves #124974. This PR introduces bounds checking for the
seq_len_maxparameter in the core TensorFlow LSTM and LSTM gradient operations (BlockLSTMOpandBlockLSTMGradOp). Previously, an oversized or negativeseq_len_maxbypassed validation and triggered a fatal abort (SIGABRT) inTensor::Slice. The parameter is now validated to ensure it falls within the bounds of[0, timelen].