CVE-2023-47108: Allocation of Resources Without Limits or Throttling (CWE-770)
value: OpenTelemetry-Go Contrib is a collection of third-party packages for OpenTelemetry-Go. Prior to version 0.46.0, the grpc Unary Server Interceptor out of the box adds labels `net.peer.sock.addr` and `net.peer.sock.port` that have unbound cardinality. It leads to the server's potential memory exhaustion when many malicious requests are sent. An attacker can easily flood the peer address and port for requests. Version 0.46.0 contains a fix for this issue. As a workaround to stop being affected, a view removing the attributes can be used. The other possibility is to disable grpc metrics instrumentation by passing `otelgrpc.WithMeterProvider` option with `noop.NewMeterProvider`.
CWE description: The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
CVSS v3 base score: 7.5
Published: 2023-11-10T19:15Z
Repository: https://github.com/open-telemetry/opentelemetry-go-contrib
Language: Go
Files changed: CHANGELOG.md, instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go, instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_test.go
Commit date: 2023-11-07 00:34:22 +0100
Commit message: otelgrpc: Remove high cardinality metric attributes (#4322)


Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57306076..5741c654 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,10 +44,14 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ### Changed
 
 - Upgrade dependencies of OpenTelemetry Go to use the new [`v1.19.0`/`v0.42.0`/`v0.0.7` release](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.19.0).
 - Use `grpc.StatsHandler` for gRPC instrumentation in `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/example`. (#4325)
 
+### Removed
+
+- The `net.sock.peer.*` and `net.peer.*` high cardinality attributes are removed from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4322)
+
 ## [1.19.0/0.44.0/0.13.0] - 2023-09-12
 
 ### Added
 
 - Add `gcp.gce.instance.name` and `gcp.gce.instance.hostname` resource attributes to `go.opentelemetry.io/contrib/detectors/gcp`. (#4263)
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
index 031d1f4d..c737d29d 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/interceptor.go
@@ -81,11 +81,11 @@ func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
 		}
 		if cfg.Filter != nil && !cfg.Filter(i) {
 			return invoker(ctx, method, req, reply, cc, callOpts...)
 		}
 
-		name, attr := spanInfo(method, cc.Target())
+		name, attr, _ := telemetryAttributes(method, cc.Target())
 
 		startOpts := append([]trace.SpanStartOption{
 			trace.WithSpanKind(trace.SpanKindClient),
 			trace.WithAttributes(attr...),
 		},
@@ -275,11 +275,11 @@ func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor {
 		}
 		if cfg.Filter != nil && !cfg.Filter(i) {
 			return streamer(ctx, desc, cc, method, callOpts...)
 		}
 
-		name, attr := spanInfo(method, cc.Target())
+		name, attr, _ := telemetryAttributes(method, cc.Target())
 
 		startOpts := append([]trace.SpanStartOption{
 			trace.WithSpanKind(trace.SpanKindClient),
 			trace.WithAttributes(attr...),
 		},
@@ -344,11 +344,11 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
 		if cfg.Filter != nil && !cfg.Filter(i) {
 			return handler(ctx, req)
 		}
 
 		ctx = extract(ctx, cfg.Propagators)
-		name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx))
+		name, attr, metricAttrs := telemetryAttributes(info.FullMethod, peerFromCtx(ctx))
 
 		startOpts := append([]trace.SpanStartOption{
 			trace.WithSpanKind(trace.SpanKindServer),
 			trace.WithAttributes(attr...),
 		},
@@ -384,12 +384,12 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
 		}
 		grpcStatusCodeAttr := statusCodeAttr(s.Code())
 		span.SetAttributes(grpcStatusCodeAttr)
 
 		elapsedTime := time.Since(before).Milliseconds()
-		attr = append(attr, grpcStatusCodeAttr)
-		cfg.rpcDuration.Record(ctx, float64(elapsedTime), metric.WithAttributes(attr...))
+		metricAttrs = append(metricAttrs, grpcStatusCodeAttr)
+		cfg.rpcDuration.Record(ctx, float64(elapsedTime), metric.WithAttributes(metricAttrs...))
 
 		return resp, err
 	}
 }
 
@@ -466,11 +466,11 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
 		if cfg.Filter != nil && !cfg.Filter(i) {
 			return handler(srv, wrapServerStream(ctx, ss, cfg))
 		}
 
 		ctx = extract(ctx, cfg.Propagators)
-		name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx))
+		name, attr, _ := telemetryAttributes(info.FullMethod, peerFromCtx(ctx))
 
 		startOpts := append([]trace.SpanStartOption{
 			trace.WithSpanKind(trace.SpanKindServer),
 			trace.WithAttributes(attr...),
 		},
@@ -496,21 +496,22 @@ func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor {
 
 		return err
 	}
 }
 
-// spanInfo returns a span name and all appropriate attributes from the gRPC
-// method and peer address.
-func spanInfo(fullMethod, peerAddress string) (string, []attribute.KeyValue) {
-	name, mAttrs := internal.ParseFullMethod(fullMethod)
+// telemetryAttributes returns a span name and span and metric attributes from
+// the gRPC method and peer address.
+func telemetryAttributes(fullMethod, peerAddress string) (string, []attribute.KeyValue, []attribute.KeyValue) {
+	name, methodAttrs := internal.ParseFullMethod(fullMethod)
 	peerAttrs := peerAttr(peerAddress)
 
-	attrs := make([]attribute.KeyValue, 0, 1+len(mAttrs)+len(peerAttrs))
+	attrs := make([]attribute.KeyValue, 0, 1+len(methodAttrs)+len(peerAttrs))
 	attrs = append(attrs, RPCSystemGRPC)
-	attrs = append(attrs, mAttrs...)
+	attrs = append(attrs, methodAttrs...)
+	metricAttrs := attrs[:1+len(methodAttrs)]
 	attrs = append(attrs, peerAttrs...)
-	return name, attrs
+	return name, attrs, metricAttrs
 }
 
 // peerAttr returns attributes about the peer address.
 func peerAttr(addr string) []attribute.KeyValue {
 	host, p, err := net.SplitHostPort(addr)
diff --git a/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_test.go b/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_test.go
index c64f59c7..6d8a8267 100644
--- a/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_test.go
+++ b/instrumentation/google.golang.org/grpc/otelgrpc/test/grpc_test.go
@@ -14,11 +14,10 @@
 
 package test
 
 import (
 	"context"
-	"fmt"
 	"net"
 	"strconv"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -663,16 +662,10 @@ func checkUnaryServerRecords(t *testing.T, reader metric.Reader) {
 	rm := metricdata.ResourceMetrics{}
 	err := reader.Collect(context.Background(), &rm)
 	assert.NoError(t, err)
 	require.Len(t, rm.ScopeMetrics, 1)
 
-	// TODO: Remove these #4322
-	address, ok := findScopeMetricAttribute(rm.ScopeMetrics[0], semconv.NetSockPeerAddrKey)
-	assert.True(t, ok)
-	port, ok := findScopeMetricAttribute(rm.ScopeMetrics[0], semconv.NetSockPeerPortKey)
-	assert.True(t, ok)
-
 	want := metricdata.ScopeMetrics{
 		Scope: wantInstrumentationScope,
 		Metrics: []metricdata.Metrics{
 			{
 				Name:        "rpc.server.duration",
@@ -685,22 +678,18 @@ func checkUnaryServerRecords(t *testing.T, reader metric.Reader) {
 							Attributes: attribute.NewSet(
 								semconv.RPCMethod("EmptyCall"),
 								semconv.RPCService("grpc.testing.TestService"),
 								otelgrpc.RPCSystemGRPC,
 								otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)),
-								address,
-								port,
 							),
 						},
 						{
 							Attributes: attribute.NewSet(
 								semconv.RPCMethod("UnaryCall"),
 								semconv.RPCService("grpc.testing.TestService"),
 								otelgrpc.RPCSystemGRPC,
 								otelgrpc.GRPCStatusCodeKey.Int64(int64(codes.OK)),
-								address,
-								port,
 							),
 						},
 					},
 				},
 			},
@@ -716,28 +705,5 @@ func findAttribute(kvs []attribute.KeyValue, key attribute.Key) (attribute.KeyVa
 			return kv, true
 		}
 	}
 	return attribute.KeyValue{}, false
 }
-
-func findScopeMetricAttribute(sm metricdata.ScopeMetrics, key attribute.Key) (attribute.KeyValue, bool) {
-	for _, m := range sm.Metrics {
-		// This only needs to cover data types used by the instrumentation.
-		switch d := m.Data.(type) {
-		case metricdata.Histogram[int64]:
-			for _, dp := range d.DataPoints {
-				if kv, ok := findAttribute(dp.Attributes.ToSlice(), key); ok {
-					return kv, true
-				}
-			}
-		case metricdata.Histogram[float64]:
-			for _, dp := range d.DataPoints {
-				if kv, ok := findAttribute(dp.Attributes.ToSlice(), key); ok {
-					return kv, true
-				}
-			}
-		default:
-			panic(fmt.Sprintf("unexpected data type %T - name %s", d, m.Name))
-		}
-	}
-	return attribute.KeyValue{}, false
-}

Vulnerable code: 		name, attr := spanInfo(method, cc.Target())
		name, attr := spanInfo(method, cc.Target())
		name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx))
		attr = append(attr, grpcStatusCodeAttr)
		cfg.rpcDuration.Record(ctx, float64(elapsedTime), metric.WithAttributes(attr...))
		name, attr := spanInfo(info.FullMethod, peerFromCtx(ctx))
// spanInfo returns a span name and all appropriate attributes from the gRPC
// method and peer address.
func spanInfo(fullMethod, peerAddress string) (string, []attribute.KeyValue) {
	name, mAttrs := internal.ParseFullMethod(fullMethod)
	attrs := make([]attribute.KeyValue, 0, 1+len(mAttrs)+len(peerAttrs))
	attrs = append(attrs, mAttrs...)
	return name, attrs
	"fmt"
	// TODO: Remove these #4322
	address, ok := findScopeMetricAttribute(rm.ScopeMetrics[0], semconv.NetSockPeerAddrKey)
	assert.True(t, ok)
	port, ok := findScopeMetricAttribute(rm.ScopeMetrics[0], semconv.NetSockPeerPortKey)
	assert.True(t, ok)

								address,
								port,
								address,
								port,

func findScopeMetricAttribute(sm metricdata.ScopeMetrics, key attribute.Key) (attribute.KeyValue, bool) {
	for _, m := range sm.Metrics {
		// This only needs to cover data types used by the instrumentation.
		switch d := m.Data.(type) {
		case metricdata.Histogram[int64]:
			for _, dp := range d.DataPoints {
				if kv, ok := findAttribute(dp.Attributes.ToSlice(), key); ok {
					return kv, true
				}
			}
		case metricdata.Histogram[float64]:
			for _, dp := range d.DataPoints {
				if kv, ok := findAttribute(dp.Attributes.ToSlice(), key); ok {
					return kv, true
				}
			}
		default:
			panic(fmt.Sprintf("unexpected data type %T - name %s", d, m.Name))
		}
	}
	return attribute.KeyValue{}, false
}
Fixed code: ### Removed

- The `net.sock.peer.*` and `net.peer.*` high cardinality attributes are removed from the metrics generated by `go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc`. (#4322)

		name, attr, _ := telemetryAttributes(method, cc.Target())
		name, attr, _ := telemetryAttributes(method, cc.Target())
		name, attr, metricAttrs := telemetryAttributes(info.FullMethod, peerFromCtx(ctx))
		metricAttrs = append(metricAttrs, grpcStatusCodeAttr)
		cfg.rpcDuration.Record(ctx, float64(elapsedTime), metric.WithAttributes(metricAttrs...))
		name, attr, _ := telemetryAttributes(info.FullMethod, peerFromCtx(ctx))
// telemetryAttributes returns a span name and span and metric attributes from
// the gRPC method and peer address.
func telemetryAttributes(fullMethod, peerAddress string) (string, []attribute.KeyValue, []attribute.KeyValue) {
	name, methodAttrs := internal.ParseFullMethod(fullMethod)
	attrs := make([]attribute.KeyValue, 0, 1+len(methodAttrs)+len(peerAttrs))
	attrs = append(attrs, methodAttrs...)
	metricAttrs := attrs[:1+len(methodAttrs)]
	return name, attrs, metricAttrs