Reject Duration strings carrying more than nanosecond precision (#30446)

TimeUtil::FromString accepted fractional seconds with more than nine digits and silently produced a wrong Duration. The scaling step computes Pow(10, 9 - nanos_part.length()); when the fraction is longer than nine digits the exponent is negative, and Pow() treats a negative exponent as zero and returns 1. The unscaled digits are then handed to set_nanos, which truncates them to 32 bits, and IsDurationValid is happy with the result.

Concretely, before the change:

    TimeUtil::FromString("0.3294967297s", &d) -> true, d = { seconds: 0, nanos: -999999999 }
    TimeUtil::FromString("1.0000000001s", &d) -> true, d = { seconds: 1, nanos: 1 }

The first case flips the sign of the parsed value outright, since 3294967297 truncated to int32 is -999999999. The second case silently moves a digit: 1.0000000001s becomes 1.000000001s. A caller that parses attacker supplied duration text gets a valid looking message with an arbitrary value.

The fix rejects any fraction longer than nine digits, the most a Duration can represent, matching the JSON mapping rule that caps fractional seconds at nine digits. Values with nine digits or fewer are unchanged.

Testing: added assertions to DurationStringFormat in src/google/protobuf/util/time_util_test.cc. On unpatched main the five new FromString calls all return true (the EXPECT_FALSE lines fail); with the fix the full TimeUtilTest suite passes, 14 tests locally.

Closes #30446

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/30446 from ManoharPaturi:fix-timeutil-duration-precision 7cd00b90a52fead245e8cc7dead270dba1b08887
PiperOrigin-RevId: 1002869518
diff --git a/src/google/protobuf/util/time_util.cc b/src/google/protobuf/util/time_util.cc
index d615545..22f9d25 100644
--- a/src/google/protobuf/util/time_util.cc
+++ b/src/google/protobuf/util/time_util.cc
@@ -264,6 +264,13 @@
   if (end != nanos_part.c_str() + nanos_part.length()) {
     return false;
   }
+  // Fractional digits beyond nano-second precision cannot be represented.
+  // With more than 9 digits the exponent below would be negative, which
+  // Pow() silently treats as 0, leaving the value unscaled before it is
+  // truncated to 32 bits (e.g. "0.3294967297s" parsed as -999999999ns).
+  if (nanos_part.length() > 9) {
+    return false;
+  }
   nanos = nanos * Pow(10, static_cast<int>(9 - nanos_part.length()));
   if (negative) {
     // If a Duration is negative, both seconds and nanos should be negative.
diff --git a/src/google/protobuf/util/time_util_test.cc b/src/google/protobuf/util/time_util_test.cc
index 0b3528d..9ef74e5 100644
--- a/src/google/protobuf/util/time_util_test.cc
+++ b/src/google/protobuf/util/time_util_test.cc
@@ -72,6 +72,20 @@
 
 TEST(TimeUtilTest, DurationStringFormat) {
   Timestamp begin, end;
+
+  // Fractional digits beyond nano-second precision cannot be represented and
+  // must be rejected: the value used to be mis-scaled and then truncated to
+  // 32 bits, silently producing a wrong (often sign-flipped) Duration.
+  {
+    Duration duration;
+    EXPECT_FALSE(TimeUtil::FromString("0.3294967297s", &duration));
+    EXPECT_FALSE(TimeUtil::FromString("-0.3294967297s", &duration));
+    EXPECT_FALSE(TimeUtil::FromString("0.4294967296s", &duration));
+    EXPECT_FALSE(TimeUtil::FromString("1.0000000001s", &duration));
+    EXPECT_FALSE(TimeUtil::FromString("0.99999999999999999999s", &duration));
+    EXPECT_TRUE(TimeUtil::FromString("0.999999999s", &duration));
+    EXPECT_EQ(999999999, duration.nanos());
+  }
   EXPECT_TRUE(TimeUtil::FromString("0001-01-01T00:00:00Z", &begin));
   EXPECT_TRUE(TimeUtil::FromString("9999-12-31T23:59:59.999999999Z", &end));