From ec15ddf2b487b7bf5f054e7461d7b95a6a0997a9 Mon Sep 17 00:00:00 2001 From: Matthew Pendrey Date: Wed, 29 Oct 2025 16:59:37 +0000 Subject: [PATCH 1/2] fix potential median consensus attack vector --- consensus/oracle/consensus_execution.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/consensus/oracle/consensus_execution.go b/consensus/oracle/consensus_execution.go index 09b00e094..575afdb02 100644 --- a/consensus/oracle/consensus_execution.go +++ b/consensus/oracle/consensus_execution.go @@ -144,7 +144,7 @@ func handleFieldsMapAggregation( } func handleMedianAggregation( - _ logger.Logger, + lggr logger.Logger, observations []*valuespb.Value, f int, ) (*valuespb.Value, error) { @@ -163,7 +163,7 @@ func handleMedianAggregation( switch medianType { case typeUint64: - medianResult, err = getMedian( + medianResult, err = getMedian(lggr, filtered, func(val *valuespb.Value) (uint64, error) { return val.GetUint64Value(), nil @@ -184,7 +184,7 @@ func handleMedianAggregation( } case typeInt64: - medianResult, err = getMedian( + medianResult, err = getMedian(lggr, filtered, func(val *valuespb.Value) (int64, error) { return val.GetInt64Value(), nil @@ -205,7 +205,7 @@ func handleMedianAggregation( } case typeFloat64: - medianResult, err = getMedian( + medianResult, err = getMedian(lggr, filtered, func(val *valuespb.Value) (float64, error) { return val.GetFloat64Value(), nil @@ -226,7 +226,7 @@ func handleMedianAggregation( } case typeDecimal: - medianResult, err = getMedian( + medianResult, err = getMedian(lggr, filtered, func(val *valuespb.Value) (decimal.Decimal, error) { var d decimal.Decimal @@ -246,7 +246,7 @@ func handleMedianAggregation( } case typeBigInt: - medianResult, err = getMedian( + medianResult, err = getMedian(lggr, filtered, func(val *valuespb.Value) (*big.Int, error) { var got big.Int @@ -266,7 +266,7 @@ func handleMedianAggregation( } case typeTime: - medianResult, err = getMedian( + medianResult, err = getMedian(lggr, filtered, func(val *valuespb.Value) (time.Time, error) { var got time.Time @@ -496,6 +496,7 @@ func filterObservations(observationProtos []*valuespb.Value, minObservations int // // For an even number of elements, we take the left of the two middle elements. func getMedian[T any]( + lggr logger.Logger, observations []*valuespb.Value, unwrap func(val *valuespb.Value) (T, error), compare func(a, b T) int, @@ -509,9 +510,12 @@ func getMedian[T any]( for _, v := range observations { unwrapped, err := unwrap(v) if err != nil { - return nil, err + // Is possible the value could be corrupt and fail to unwrap, so log warning as this should not happen and skip + lggr.Warnf("failed to unwrap observation during median calculation: %s", err) + } else { + unwrappedValues = append(unwrappedValues, unwrapped) } - unwrappedValues = append(unwrappedValues, unwrapped) + } slices.SortFunc(unwrappedValues, compare) From 840ef19ded38b37cc3fc7068c8e24cd29e683911 Mon Sep 17 00:00:00 2001 From: Matthew Pendrey Date: Wed, 29 Oct 2025 17:02:43 +0000 Subject: [PATCH 2/2] check sufficient values after corrupt value exclusion --- consensus/oracle/consensus_execution.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/consensus/oracle/consensus_execution.go b/consensus/oracle/consensus_execution.go index 575afdb02..294584010 100644 --- a/consensus/oracle/consensus_execution.go +++ b/consensus/oracle/consensus_execution.go @@ -510,12 +510,16 @@ func getMedian[T any]( for _, v := range observations { unwrapped, err := unwrap(v) if err != nil { - // Is possible the value could be corrupt and fail to unwrap, so log warning as this should not happen and skip + // It's possible the value could be corrupt and fail to unwrap, so skip and log warning as this should not happen lggr.Warnf("failed to unwrap observation during median calculation: %s", err) } else { unwrappedValues = append(unwrappedValues, unwrapped) } + } + // As values are filtered for unwrapping errors, need to re-check the number of observations is still sufficient for consensus + if len(unwrappedValues) < f+1 { + return nil, ErrInsufficientObservations } slices.SortFunc(unwrappedValues, compare)