From 8af6c33eb931a5b926c4b831d648e18fb6e89cc4 Mon Sep 17 00:00:00 2001 From: Kazuyoshi Kato Date: Mon, 11 Apr 2022 17:02:11 -0700 Subject: [PATCH 01/16] Specify go_package in its full path (#1345) The newer version of protoc-gen-go doesn't support the current form. Signed-off-by: Kazuyoshi Kato --- internal/vmservice/vmservice.pb.go | 10 +- internal/vmservice/vmservice.proto | 2 +- .../ncproxygrpc/v1/networkconfigproxy.pb.go | 204 +++++++++--------- .../ncproxygrpc/v1/networkconfigproxy.proto | 2 +- pkg/ncproxy/nodenetsvc/v1/nodenetsvc.pb.go | 86 ++++---- pkg/ncproxy/nodenetsvc/v1/nodenetsvc.proto | 2 +- 6 files changed, 153 insertions(+), 153 deletions(-) diff --git a/internal/vmservice/vmservice.pb.go b/internal/vmservice/vmservice.pb.go index 79cf5624df..da1533f7c5 100644 --- a/internal/vmservice/vmservice.pb.go +++ b/internal/vmservice/vmservice.pb.go @@ -1621,7 +1621,7 @@ func init() { } var fileDescriptor_272f12cfdaa6c7c8 = []byte{ - // 2295 bytes of a gzipped FileDescriptorProto + // 2296 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x58, 0xcf, 0x73, 0xdb, 0xc6, 0xf5, 0x27, 0x28, 0x8a, 0x22, 0x1f, 0x7f, 0x88, 0x59, 0x39, 0x8e, 0x22, 0x67, 0x24, 0x07, 0x99, 0x8c, 0x13, 0x27, 0x91, 0xbe, 0x5f, 0xba, 0xe9, 0xd8, 0xae, 0x9b, 0x54, 0x24, 0x65, 0x93, 0x8d, @@ -1762,10 +1762,10 @@ var fileDescriptor_272f12cfdaa6c7c8 = []byte{ 0xb4, 0x0f, 0xd5, 0x74, 0xd7, 0x8d, 0x36, 0x5f, 0xdd, 0xe9, 0x6f, 0x6c, 0x5d, 0xba, 0xaf, 0x05, 0xfe, 0x12, 0xea, 0xd9, 0x84, 0x85, 0xae, 0x2f, 0x44, 0xf5, 0x5c, 0x2e, 0xbb, 0xd4, 0x62, 0x5f, 0x40, 0x29, 0x69, 0x61, 0x32, 0x0f, 0x6c, 0xae, 0xaf, 0xb9, 0x94, 0xff, 0xa7, 0x50, 0xf8, 0x3a, - 0x26, 0xfc, 0x75, 0x3d, 0xd5, 0xfa, 0xe0, 0xbb, 0xef, 0x37, 0x73, 0xff, 0xfc, 0x7e, 0x33, 0xf7, - 0xeb, 0xf3, 0x4d, 0xe3, 0xbb, 0xf3, 0x4d, 0xe3, 0x1f, 0xe7, 0x9b, 0xc6, 0xbf, 0xcf, 0x37, 0x8d, - 0x6f, 0x66, 0x3f, 0x95, 0x0f, 0x8b, 0x92, 0xe9, 0xd6, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x96, - 0xae, 0xa5, 0x20, 0xa9, 0x16, 0x00, 0x00, + 0x26, 0xfc, 0x75, 0x3d, 0xd5, 0xea, 0x7d, 0xf7, 0xfd, 0x66, 0xee, 0x9f, 0xdf, 0x6f, 0xe6, 0x7e, + 0x7d, 0xbe, 0x69, 0x7c, 0x77, 0xbe, 0x69, 0xfc, 0xe3, 0x7c, 0xd3, 0xf8, 0xf7, 0xf9, 0xa6, 0xf1, + 0xcd, 0xce, 0x6b, 0xfe, 0x95, 0x3e, 0x2c, 0x4a, 0xd1, 0xb7, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, + 0x29, 0x1c, 0xce, 0x56, 0xcf, 0x16, 0x00, 0x00, } func (m *DirectBoot) Marshal() (dAtA []byte, err error) { diff --git a/internal/vmservice/vmservice.proto b/internal/vmservice/vmservice.proto index be32b09e8b..78f4174aba 100644 --- a/internal/vmservice/vmservice.proto +++ b/internal/vmservice/vmservice.proto @@ -1,7 +1,7 @@ syntax = 'proto3'; package vmservice; -option go_package = "vmservice"; +option go_package = "github.com/Microsoft/hcsshim/internal/vmservice"; import "google/protobuf/empty.proto"; import "google/protobuf/struct.proto"; diff --git a/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.pb.go b/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.pb.go index c80b299e20..55032f1ecb 100644 --- a/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.pb.go +++ b/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.pb.go @@ -1688,108 +1688,108 @@ func init() { } var fileDescriptor_f8f0dd742f8ac3db = []byte{ - // 1606 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0x5d, 0x73, 0xdb, 0x44, - 0x17, 0xb6, 0x9c, 0x2f, 0xfb, 0x38, 0x4e, 0x9c, 0x75, 0x9c, 0x78, 0xfc, 0xb6, 0x4e, 0xaa, 0x7e, - 0x24, 0x6f, 0xdf, 0xbe, 0x09, 0x71, 0xa1, 0x37, 0x74, 0xe8, 0x24, 0x36, 0x34, 0xea, 0x4c, 0x82, - 0x51, 0xd3, 0xa1, 0x7c, 0x0c, 0x1a, 0x55, 0x5a, 0xdb, 0x3b, 0x8d, 0x25, 0x55, 0x92, 0x9d, 0xe6, - 0x8e, 0x5b, 0x66, 0xe0, 0x17, 0xf0, 0x23, 0xb8, 0xe4, 0x0e, 0x6e, 0x7b, 0x07, 0xc3, 0x15, 0xcc, - 0x30, 0x19, 0xea, 0x5f, 0xc2, 0x68, 0xb5, 0x2b, 0x4b, 0xb2, 0xec, 0x18, 0xb8, 0x01, 0xae, 0x6c, - 0x9f, 0x8f, 0xe7, 0x9c, 0x3d, 0x7b, 0xce, 0xb3, 0xbb, 0x86, 0x66, 0x9b, 0xb8, 0x9d, 0xde, 0xb3, - 0x1d, 0xcd, 0xec, 0xee, 0x1e, 0x11, 0xcd, 0x36, 0x1d, 0xb3, 0xe5, 0xee, 0x76, 0x34, 0xc7, 0xe9, - 0x90, 0xee, 0xae, 0xf5, 0xbc, 0xbd, 0x6b, 0x68, 0x96, 0x6d, 0xbe, 0x3c, 0xe7, 0x9f, 0x6d, 0xdb, - 0xd2, 0x76, 0xfb, 0x7b, 0xbb, 0x06, 0x76, 0xcf, 0x4c, 0xfb, 0xb9, 0x66, 0x1a, 0x2d, 0xd2, 0xa6, - 0x9a, 0x1d, 0xcb, 0x36, 0x5d, 0x13, 0x2d, 0x85, 0x0c, 0x77, 0xfa, 0x7b, 0xe2, 0x2f, 0x02, 0xe4, - 0xf7, 0x75, 0xfd, 0x58, 0xaa, 0xcb, 0xf8, 0x45, 0x0f, 0x3b, 0x2e, 0xaa, 0xc1, 0xa2, 0x66, 0x1a, - 0xae, 0x4a, 0x0c, 0x6c, 0x2b, 0x44, 0x2f, 0x0b, 0x9b, 0xc2, 0x76, 0xf6, 0x60, 0x79, 0x70, 0xb1, - 0x91, 0xab, 0x73, 0xb9, 0xd4, 0x90, 0x73, 0x81, 0x91, 0xa4, 0xa3, 0x4d, 0x98, 0x37, 0x88, 0xe6, - 0x59, 0xa7, 0xa9, 0x75, 0x76, 0x70, 0xb1, 0x31, 0x77, 0x4c, 0x34, 0xa9, 0x21, 0xcf, 0x19, 0x44, - 0x93, 0x74, 0x74, 0x1d, 0xf2, 0xd8, 0xd0, 0x2d, 0x93, 0x18, 0xae, 0x62, 0xa8, 0x5d, 0x5c, 0x9e, - 0xf1, 0x0c, 0xe5, 0x45, 0x2e, 0x3c, 0x56, 0xbb, 0x18, 0x1d, 0xc1, 0x4a, 0x60, 0xe4, 0x60, 0xd7, - 0x25, 0x46, 0xdb, 0x29, 0xcf, 0x6e, 0x0a, 0xdb, 0xb9, 0xda, 0xe6, 0x4e, 0x34, 0xf1, 0x9d, 0x77, - 0x99, 0xe1, 0x63, 0x66, 0x27, 0x17, 0x70, 0x4c, 0x22, 0x16, 0x60, 0x89, 0x2f, 0xcd, 0xb1, 0x4c, - 0xc3, 0xc1, 0xe2, 0xaf, 0x02, 0x14, 0x8e, 0x4c, 0x9d, 0xb4, 0xce, 0xff, 0x95, 0x0b, 0x2e, 0xc2, - 0x4a, 0x68, 0x75, 0x6c, 0xcd, 0x5f, 0x09, 0x50, 0x68, 0xe0, 0x53, 0xec, 0xe2, 0xbf, 0xc5, 0x9a, - 0xbd, 0x24, 0x43, 0xe9, 0xb0, 0x24, 0x25, 0x58, 0xad, 0xdb, 0x58, 0x75, 0xf1, 0xb1, 0xdf, 0xb8, - 0x3c, 0xcf, 0x3d, 0x58, 0x60, 0xad, 0x4c, 0x53, 0xcc, 0xd5, 0xd6, 0xe3, 0x65, 0xe1, 0x0e, 0xdc, - 0x4e, 0xfc, 0x56, 0x80, 0x05, 0x26, 0x44, 0x47, 0x90, 0xeb, 0x68, 0x86, 0x12, 0x85, 0xb8, 0x1d, - 0x87, 0x38, 0x34, 0x1d, 0xb7, 0x6e, 0x76, 0xad, 0x5e, 0x10, 0x9e, 0x57, 0xf4, 0x30, 0x25, 0x43, - 0x47, 0x33, 0x38, 0xdc, 0x07, 0xb0, 0xcc, 0x5c, 0x03, 0xc8, 0x34, 0x85, 0xbc, 0x35, 0x92, 0x55, - 0xbd, 0xe9, 0xfd, 0x1c, 0x85, 0xe3, 0xf3, 0xc7, 0x34, 0x07, 0x00, 0x19, 0xbe, 0xf1, 0xe2, 0x1d, - 0x58, 0x4b, 0xf6, 0x43, 0x08, 0x66, 0x69, 0x3d, 0xe9, 0x36, 0xc9, 0xf4, 0xbb, 0xf8, 0xf5, 0x0c, - 0x54, 0xc6, 0x67, 0x9e, 0xe4, 0x82, 0x1e, 0xc1, 0x6c, 0xd7, 0xd4, 0x31, 0x4d, 0x7a, 0xa9, 0x76, - 0x6f, 0xfa, 0x3a, 0xf0, 0x2a, 0x1f, 0x99, 0x3a, 0x96, 0x29, 0x06, 0xda, 0x80, 0x9c, 0x73, 0x46, - 0x5c, 0xad, 0x13, 0xde, 0x69, 0xf0, 0x45, 0xb4, 0xb7, 0x9b, 0x90, 0x25, 0x96, 0xda, 0x55, 0xdc, - 0x73, 0x0b, 0xd3, 0x9e, 0x5e, 0xaa, 0xdd, 0xfd, 0x03, 0x11, 0x25, 0x4b, 0xed, 0x9e, 0x9c, 0x5b, - 0x58, 0xce, 0x10, 0xf6, 0x0d, 0xdd, 0x83, 0x75, 0xa7, 0xf7, 0xcc, 0xc0, 0xae, 0x42, 0x2c, 0x55, - 0xd7, 0x6d, 0xec, 0x38, 0x8a, 0x65, 0xe3, 0x16, 0x79, 0x59, 0x9e, 0xdb, 0x9c, 0xd9, 0xce, 0xca, - 0x25, 0x5f, 0x2d, 0x71, 0x6d, 0x93, 0x2a, 0xd1, 0x16, 0x2c, 0xeb, 0xb8, 0xa5, 0xf6, 0x4e, 0x5d, - 0xa5, 0xad, 0xba, 0xf8, 0x4c, 0x3d, 0x2f, 0xcf, 0xd3, 0x74, 0x97, 0x98, 0xf8, 0xa1, 0x2f, 0x15, - 0xb7, 0x20, 0x17, 0x5a, 0x28, 0x5a, 0x86, 0xdc, 0x89, 0xad, 0x1a, 0x8e, 0xa5, 0xda, 0xd8, 0x70, - 0x0b, 0x29, 0xb4, 0x00, 0x33, 0xc7, 0xfb, 0x27, 0x05, 0x41, 0xdc, 0x84, 0x0c, 0xcf, 0x0f, 0x01, - 0xcc, 0x3f, 0x76, 0x55, 0x97, 0x68, 0x85, 0x14, 0xca, 0xc0, 0x6c, 0xe3, 0xb0, 0xde, 0x2c, 0x08, - 0xe2, 0x2e, 0x94, 0x62, 0x0d, 0xed, 0x77, 0x3a, 0x5a, 0x83, 0x74, 0x30, 0x6f, 0xf3, 0x83, 0x8b, - 0x8d, 0xb4, 0xd4, 0x90, 0xd3, 0x44, 0x17, 0xef, 0xc3, 0xd5, 0xa6, 0x69, 0xd3, 0x11, 0xe1, 0x93, - 0xde, 0x34, 0x4f, 0x89, 0x76, 0xce, 0x2a, 0x82, 0xfe, 0x03, 0x59, 0xcb, 0xb4, 0xd9, 0x60, 0xf9, - 0xbb, 0x9a, 0xb1, 0x98, 0x87, 0xf8, 0x8d, 0x00, 0x65, 0xc9, 0xec, 0x27, 0x7b, 0xde, 0x01, 0x44, - 0xcc, 0xbe, 0x62, 0xb6, 0x5a, 0xa7, 0xa6, 0xaa, 0x2b, 0x67, 0x98, 0xb4, 0x3b, 0x2e, 0x85, 0xc8, - 0xcb, 0x05, 0x62, 0xf6, 0xdf, 0xf7, 0x15, 0x1f, 0x52, 0x39, 0xaa, 0x41, 0xe9, 0x45, 0x0f, 0xf7, - 0xb0, 0x62, 0xa9, 0xc4, 0x76, 0x14, 0xdb, 0x9f, 0x44, 0xec, 0x4f, 0x7d, 0x5e, 0x2e, 0x52, 0x65, - 0xd3, 0xd3, 0xc9, 0x5c, 0x85, 0xf6, 0x60, 0x95, 0x18, 0x2e, 0xb6, 0xed, 0x9e, 0xe5, 0x2a, 0x5e, - 0x7b, 0xd8, 0xaa, 0x4b, 0x4c, 0x83, 0x76, 0x45, 0x5e, 0x2e, 0x06, 0xba, 0xa3, 0x40, 0x25, 0xea, - 0x00, 0x0d, 0xc3, 0xe1, 0x29, 0xde, 0x82, 0x65, 0x07, 0xdb, 0x7d, 0x8f, 0x8c, 0x2c, 0xc5, 0xdb, - 0x3d, 0xa7, 0x2c, 0xd0, 0x2d, 0xcd, 0xfb, 0x62, 0xc9, 0xda, 0xf7, 0x84, 0x68, 0x0d, 0xe6, 0x75, - 0xb3, 0xab, 0x12, 0xc3, 0xe7, 0x20, 0x99, 0xfd, 0xf2, 0xe4, 0x0e, 0x56, 0x6d, 0xad, 0x53, 0x9e, - 0xa1, 0x6e, 0xec, 0x97, 0xd8, 0xe2, 0xdb, 0xc0, 0x2b, 0xc3, 0x89, 0x25, 0x91, 0x79, 0x85, 0x3f, - 0xcd, 0xbc, 0xdf, 0x09, 0x50, 0x88, 0x9b, 0xa1, 0x43, 0x58, 0xf4, 0xd8, 0x87, 0x1b, 0x33, 0xf8, - 0xeb, 0x23, 0x43, 0xa0, 0x19, 0x71, 0xd7, 0xc3, 0x94, 0xec, 0x11, 0x17, 0x17, 0xa3, 0x13, 0x28, - 0x70, 0xe2, 0x09, 0xd0, 0x7c, 0xe6, 0xd9, 0x1a, 0xc3, 0x3c, 0x09, 0x88, 0x9c, 0xbb, 0xb8, 0x2a, - 0xc2, 0x3d, 0x5f, 0x0a, 0x50, 0x0c, 0x25, 0x12, 0xb4, 0xeb, 0x15, 0xc8, 0x7a, 0x0d, 0xe7, 0x58, - 0xaa, 0xc6, 0xbb, 0x6e, 0x28, 0x60, 0xcd, 0x9c, 0x8e, 0x37, 0x33, 0x7a, 0x30, 0x44, 0xa6, 0x3d, - 0x30, 0xdd, 0xaa, 0xe5, 0x61, 0x3a, 0xdf, 0xa7, 0x23, 0xe9, 0x4c, 0x64, 0xb5, 0x2a, 0x40, 0x57, - 0xd5, 0xd8, 0xc8, 0xb3, 0xbe, 0x08, 0x49, 0xbc, 0x25, 0x04, 0x7c, 0xc1, 0x78, 0x6a, 0x28, 0x40, - 0x6f, 0xc1, 0x5a, 0x9c, 0x4d, 0x4e, 0xb1, 0xd1, 0x76, 0x3b, 0x94, 0xb3, 0xf2, 0x72, 0x89, 0x44, - 0xd9, 0xc4, 0x57, 0xa2, 0x6b, 0xb0, 0xc8, 0x8e, 0x00, 0x7f, 0x20, 0xe7, 0x28, 0x6e, 0x8e, 0xc9, - 0x28, 0x01, 0x3e, 0x80, 0x8c, 0xe5, 0xcd, 0x21, 0xc1, 0x0e, 0xe5, 0x9b, 0xc9, 0x45, 0x68, 0x32, - 0x53, 0x39, 0x70, 0x42, 0x6f, 0x43, 0x4e, 0x37, 0x1c, 0xde, 0x9e, 0xe5, 0x05, 0x8a, 0x51, 0x89, - 0x63, 0x0c, 0xa7, 0x48, 0x06, 0x3d, 0xf8, 0xee, 0x5d, 0x75, 0x8a, 0x09, 0xf0, 0x08, 0xc3, 0xba, - 0xc7, 0x1a, 0x5e, 0xd2, 0x0a, 0x8d, 0x74, 0x1e, 0x04, 0xf0, 0xfb, 0xf3, 0xff, 0xf1, 0x00, 0x13, - 0x69, 0x49, 0x2e, 0x71, 0xb4, 0x28, 0xe7, 0x3c, 0x85, 0xa2, 0xc7, 0x39, 0xd1, 0x08, 0x0e, 0x6b, - 0xda, 0xed, 0x78, 0x88, 0x71, 0xd4, 0x25, 0xaf, 0x10, 0xb3, 0x1f, 0x91, 0x38, 0x8f, 0x66, 0x33, - 0x33, 0x85, 0x8c, 0xf8, 0x53, 0x1a, 0xd6, 0xc7, 0xb4, 0xfa, 0x3f, 0xaa, 0x49, 0xa6, 0x3d, 0x9b, - 0x50, 0x13, 0x90, 0xa5, 0x11, 0x45, 0xc7, 0x7d, 0xa2, 0x61, 0x45, 0xc7, 0xae, 0x4a, 0x4e, 0x1d, - 0xd6, 0x13, 0x23, 0x8c, 0xd5, 0xac, 0x4b, 0x0d, 0x6a, 0xd8, 0xf0, 0xed, 0x0e, 0x53, 0x72, 0xc1, - 0xd2, 0x48, 0x44, 0x76, 0x50, 0x80, 0xa5, 0x28, 0x9a, 0xe8, 0x40, 0x21, 0xee, 0x89, 0xfe, 0x0b, - 0x59, 0x66, 0x15, 0x1c, 0x5b, 0x8b, 0x83, 0x8b, 0x8d, 0x8c, 0x6f, 0x25, 0x35, 0xe4, 0x8c, 0xaf, - 0x96, 0x74, 0xf4, 0x26, 0xac, 0xf5, 0x89, 0xed, 0xf6, 0xd4, 0x53, 0xa5, 0xd5, 0x33, 0x34, 0x8f, - 0xe6, 0x15, 0x62, 0xe8, 0xf8, 0x25, 0x3b, 0x3a, 0x56, 0x99, 0xf6, 0x3d, 0xa6, 0x94, 0x3c, 0x9d, - 0xf8, 0x06, 0xac, 0xc5, 0x29, 0xfa, 0x92, 0xa3, 0xf2, 0x53, 0x40, 0xfb, 0xba, 0x1e, 0x67, 0xf4, - 0xa4, 0x5d, 0xaf, 0xc1, 0x62, 0x40, 0x56, 0xc3, 0x8b, 0x2b, 0xbd, 0xe6, 0x1e, 0x73, 0xb9, 0x77, - 0xcd, 0x0d, 0x8c, 0x24, 0x5d, 0x2c, 0x41, 0x31, 0x82, 0xce, 0x6e, 0xa8, 0xff, 0x83, 0x92, 0x7f, - 0x6d, 0x9d, 0x22, 0xae, 0x58, 0x86, 0xb5, 0xb8, 0x31, 0x83, 0xb9, 0x0d, 0xab, 0xec, 0xf6, 0x1b, - 0xbd, 0xe8, 0x26, 0xa1, 0xac, 0xf3, 0x90, 0xb1, 0x3b, 0x84, 0xb8, 0x0d, 0xe8, 0x21, 0x76, 0xa7, - 0x49, 0xe4, 0x0b, 0x01, 0x8a, 0x11, 0xd3, 0xbf, 0x44, 0xeb, 0xf7, 0x21, 0x13, 0x1c, 0x3f, 0x33, - 0x53, 0x9e, 0x95, 0x81, 0x87, 0xb8, 0x05, 0x2b, 0x0f, 0xb1, 0x3b, 0xc5, 0xba, 0x15, 0xba, 0xbc, - 0x29, 0x2f, 0x4e, 0xe1, 0x27, 0x42, 0x7a, 0xca, 0x27, 0x42, 0x29, 0x52, 0x14, 0x7e, 0x8f, 0x11, - 0x3f, 0x82, 0xd5, 0xa8, 0x98, 0x45, 0xde, 0x87, 0x2c, 0x5f, 0x84, 0x7f, 0x2d, 0x49, 0x60, 0xf2, - 0x84, 0x22, 0xcb, 0x43, 0x2f, 0x71, 0x35, 0xbc, 0xa4, 0x20, 0xe0, 0x13, 0x9a, 0xc7, 0x50, 0xca, - 0xe2, 0xbd, 0x03, 0x19, 0x96, 0x29, 0x0f, 0x27, 0x26, 0x84, 0x8b, 0xd5, 0x47, 0x0e, 0x7c, 0x6a, - 0x3f, 0x64, 0x00, 0x31, 0x6d, 0x9d, 0xfe, 0x01, 0x40, 0x69, 0x12, 0x49, 0x30, 0xef, 0x3f, 0x87, - 0xd1, 0xd5, 0x38, 0x5c, 0xe4, 0x1f, 0x80, 0x4a, 0x75, 0x9c, 0x9a, 0xb5, 0x5f, 0x0a, 0xc9, 0x90, - 0x0d, 0x1e, 0x9a, 0x68, 0xa4, 0x07, 0xe2, 0x2f, 0xec, 0xca, 0xb5, 0x09, 0x16, 0x61, 0xcc, 0xe0, - 0x5d, 0x38, 0x8a, 0x19, 0x7f, 0xc1, 0x8e, 0x62, 0x8e, 0x3e, 0x2a, 0x53, 0xe8, 0x33, 0xc8, 0x47, - 0x6e, 0xe1, 0xe8, 0x46, 0xdc, 0x2b, 0xe9, 0xd5, 0x59, 0xb9, 0x79, 0x89, 0x55, 0x80, 0xaf, 0xc2, - 0x52, 0x94, 0xbb, 0xd0, 0x18, 0xd7, 0xd8, 0xac, 0x56, 0x6e, 0x5d, 0x66, 0x16, 0x84, 0x78, 0x0a, - 0xb9, 0x10, 0x1d, 0x21, 0x31, 0x61, 0x6f, 0xe2, 0xe0, 0xd7, 0x27, 0xda, 0x84, 0x93, 0x8f, 0x92, - 0xd4, 0x68, 0xf2, 0x89, 0x8c, 0x37, 0x9a, 0xfc, 0x18, 0xae, 0xa3, 0xf5, 0x8f, 0x30, 0xd8, 0x68, - 0xfd, 0x93, 0xc8, 0xb0, 0x72, 0xf3, 0x12, 0xab, 0x70, 0x71, 0x42, 0x83, 0x87, 0xc4, 0x89, 0x53, - 0x39, 0xa6, 0x38, 0x09, 0x93, 0x2b, 0xa6, 0xd0, 0x13, 0x80, 0xe1, 0x8c, 0xa1, 0x6b, 0x93, 0xe6, - 0xcf, 0xc7, 0x9d, 0x62, 0x44, 0xc5, 0x14, 0xfa, 0x04, 0x16, 0xc3, 0x14, 0x83, 0x26, 0x65, 0xc3, - 0x69, 0xa2, 0x72, 0x63, 0xb2, 0x51, 0xac, 0x1a, 0x9c, 0x4e, 0xd0, 0x84, 0x8c, 0x9c, 0x49, 0xd5, - 0x88, 0xf3, 0x91, 0x98, 0x3a, 0xb8, 0xf2, 0xea, 0x75, 0x35, 0xf5, 0xf3, 0xeb, 0x6a, 0xea, 0xf3, - 0x41, 0x55, 0x78, 0x35, 0xa8, 0x0a, 0x3f, 0x0e, 0xaa, 0xc2, 0x6f, 0x83, 0xaa, 0xf0, 0x71, 0xba, - 0xbf, 0xf7, 0x6c, 0x9e, 0xfe, 0xb5, 0x78, 0xf7, 0xf7, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x98, - 0x89, 0x70, 0xae, 0x14, 0x00, 0x00, + // 1610 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0x4b, 0x6f, 0xdb, 0xc6, + 0x16, 0x16, 0xe5, 0x97, 0x74, 0x64, 0xd9, 0xf2, 0xc8, 0xb2, 0x05, 0xdd, 0x1b, 0xd9, 0x61, 0x1e, + 0xf6, 0xcd, 0xcd, 0xb5, 0xaf, 0x95, 0x7b, 0xd3, 0x45, 0x83, 0x06, 0xb6, 0xd4, 0xc6, 0x0c, 0x60, + 0x57, 0x61, 0x1c, 0x34, 0x7d, 0xa0, 0x04, 0x43, 0x8e, 0xa4, 0x41, 0x2c, 0x92, 0x21, 0x29, 0x39, + 0xde, 0x75, 0x5b, 0xa0, 0xfd, 0x05, 0xfd, 0x11, 0x5d, 0x76, 0xd7, 0x6e, 0xb3, 0x6b, 0xd1, 0x55, + 0x0b, 0x14, 0x46, 0xa3, 0x5f, 0x52, 0x70, 0x38, 0x43, 0x91, 0x14, 0x25, 0xab, 0xe9, 0xa6, 0xed, + 0xca, 0xd6, 0x79, 0x7c, 0xe7, 0xcc, 0x99, 0x73, 0xbe, 0x99, 0x21, 0x34, 0xdb, 0xc4, 0xed, 0xf4, + 0x9e, 0xed, 0x68, 0x66, 0x77, 0xf7, 0x88, 0x68, 0xb6, 0xe9, 0x98, 0x2d, 0x77, 0xb7, 0xa3, 0x39, + 0x4e, 0x87, 0x74, 0x77, 0xad, 0xe7, 0xed, 0x5d, 0x43, 0xb3, 0x6c, 0xf3, 0xe5, 0x39, 0xff, 0xdb, + 0xb6, 0x2d, 0x6d, 0xb7, 0xbf, 0xb7, 0x6b, 0x60, 0xf7, 0xcc, 0xb4, 0x9f, 0x6b, 0xa6, 0xd1, 0x22, + 0x6d, 0xaa, 0xd9, 0xb1, 0x6c, 0xd3, 0x35, 0xd1, 0x52, 0xc8, 0x70, 0xa7, 0xbf, 0x27, 0xfe, 0x2c, + 0x40, 0x7e, 0x5f, 0xd7, 0x8f, 0xa5, 0xba, 0x8c, 0x5f, 0xf4, 0xb0, 0xe3, 0xa2, 0x1a, 0x2c, 0x6a, + 0xa6, 0xe1, 0xaa, 0xc4, 0xc0, 0xb6, 0x42, 0xf4, 0xb2, 0xb0, 0x29, 0x6c, 0x67, 0x0f, 0x96, 0x07, + 0x17, 0x1b, 0xb9, 0x3a, 0x97, 0x4b, 0x0d, 0x39, 0x17, 0x18, 0x49, 0x3a, 0xda, 0x84, 0x79, 0x83, + 0x68, 0x9e, 0x75, 0x9a, 0x5a, 0x67, 0x07, 0x17, 0x1b, 0x73, 0xc7, 0x44, 0x93, 0x1a, 0xf2, 0x9c, + 0x41, 0x34, 0x49, 0x47, 0xd7, 0x20, 0x8f, 0x0d, 0xdd, 0x32, 0x89, 0xe1, 0x2a, 0x86, 0xda, 0xc5, + 0xe5, 0x19, 0xcf, 0x50, 0x5e, 0xe4, 0xc2, 0x63, 0xb5, 0x8b, 0xd1, 0x11, 0xac, 0x04, 0x46, 0x0e, + 0x76, 0x5d, 0x62, 0xb4, 0x9d, 0xf2, 0xec, 0xa6, 0xb0, 0x9d, 0xab, 0x6d, 0xee, 0x44, 0x13, 0xdf, + 0x79, 0x97, 0x19, 0x3e, 0x66, 0x76, 0x72, 0x01, 0xc7, 0x24, 0x62, 0x01, 0x96, 0xf8, 0xd2, 0x1c, + 0xcb, 0x34, 0x1c, 0x2c, 0xfe, 0x22, 0x40, 0xe1, 0xc8, 0xd4, 0x49, 0xeb, 0xfc, 0x6f, 0xb9, 0xe0, + 0x22, 0xac, 0x84, 0x56, 0xc7, 0xd6, 0xfc, 0xa5, 0x00, 0x85, 0x06, 0x3e, 0xc5, 0x2e, 0xfe, 0x53, + 0xac, 0xd9, 0x4b, 0x32, 0x94, 0x0e, 0x4b, 0x52, 0x82, 0xd5, 0xba, 0x8d, 0x55, 0x17, 0x1f, 0xfb, + 0x8d, 0xcb, 0xf3, 0xdc, 0x83, 0x05, 0xd6, 0xca, 0x34, 0xc5, 0x5c, 0x6d, 0x3d, 0x5e, 0x16, 0xee, + 0xc0, 0xed, 0xc4, 0x6f, 0x04, 0x58, 0x60, 0x42, 0x74, 0x04, 0xb9, 0x8e, 0x66, 0x28, 0x51, 0x88, + 0x5b, 0x71, 0x88, 0x43, 0xd3, 0x71, 0xeb, 0x66, 0xd7, 0xea, 0x05, 0xe1, 0x79, 0x45, 0x0f, 0x53, + 0x32, 0x74, 0x34, 0x83, 0xc3, 0x3d, 0x82, 0x65, 0xe6, 0x1a, 0x40, 0xa6, 0x29, 0xe4, 0xcd, 0x91, + 0xac, 0xea, 0x4d, 0xef, 0xe7, 0x28, 0x1c, 0x9f, 0x3f, 0xa6, 0x39, 0x00, 0xc8, 0xf0, 0x8d, 0x17, + 0x6f, 0xc3, 0x5a, 0xb2, 0x1f, 0x42, 0x30, 0x4b, 0xeb, 0x49, 0xb7, 0x49, 0xa6, 0xff, 0x8b, 0x5f, + 0xcd, 0x40, 0x65, 0x7c, 0xe6, 0x49, 0x2e, 0xe8, 0x21, 0xcc, 0x76, 0x4d, 0x1d, 0xd3, 0xa4, 0x97, + 0x6a, 0x77, 0xa7, 0xaf, 0x03, 0xaf, 0xf2, 0x91, 0xa9, 0x63, 0x99, 0x62, 0xa0, 0x0d, 0xc8, 0x39, + 0x67, 0xc4, 0xd5, 0x3a, 0xe1, 0x9d, 0x06, 0x5f, 0x44, 0x7b, 0xbb, 0x09, 0x59, 0x62, 0xa9, 0x5d, + 0xc5, 0x3d, 0xb7, 0x30, 0xed, 0xe9, 0xa5, 0xda, 0x9d, 0xdf, 0x11, 0x51, 0xb2, 0xd4, 0xee, 0xc9, + 0xb9, 0x85, 0xe5, 0x0c, 0x61, 0xff, 0xa1, 0xbb, 0xb0, 0xee, 0xf4, 0x9e, 0x19, 0xd8, 0x55, 0x88, + 0xa5, 0xea, 0xba, 0x8d, 0x1d, 0x47, 0xb1, 0x6c, 0xdc, 0x22, 0x2f, 0xcb, 0x73, 0x9b, 0x33, 0xdb, + 0x59, 0xb9, 0xe4, 0xab, 0x25, 0xae, 0x6d, 0x52, 0x25, 0xda, 0x82, 0x65, 0x1d, 0xb7, 0xd4, 0xde, + 0xa9, 0xab, 0xb4, 0x55, 0x17, 0x9f, 0xa9, 0xe7, 0xe5, 0x79, 0x9a, 0xee, 0x12, 0x13, 0x3f, 0xf0, + 0xa5, 0xe2, 0x16, 0xe4, 0x42, 0x0b, 0x45, 0xcb, 0x90, 0x3b, 0xb1, 0x55, 0xc3, 0xb1, 0x54, 0x1b, + 0x1b, 0x6e, 0x21, 0x85, 0x16, 0x60, 0xe6, 0x78, 0xff, 0xa4, 0x20, 0x88, 0x9b, 0x90, 0xe1, 0xf9, + 0x21, 0x80, 0xf9, 0xc7, 0xae, 0xea, 0x12, 0xad, 0x90, 0x42, 0x19, 0x98, 0x6d, 0x1c, 0xd6, 0x9b, + 0x05, 0x41, 0xdc, 0x85, 0x52, 0xac, 0xa1, 0xfd, 0x4e, 0x47, 0x6b, 0x90, 0x0e, 0xe6, 0x6d, 0x7e, + 0x70, 0xb1, 0x91, 0x96, 0x1a, 0x72, 0x9a, 0xe8, 0xe2, 0x3d, 0xb8, 0xd2, 0x34, 0x6d, 0x3a, 0x22, + 0x7c, 0xd2, 0x9b, 0xe6, 0x29, 0xd1, 0xce, 0x59, 0x45, 0xd0, 0x3f, 0x20, 0x6b, 0x99, 0x36, 0x1b, + 0x2c, 0x7f, 0x57, 0x33, 0x16, 0xf3, 0x10, 0xbf, 0x16, 0xa0, 0x2c, 0x99, 0xfd, 0x64, 0xcf, 0xdb, + 0x80, 0x88, 0xd9, 0x57, 0xcc, 0x56, 0xeb, 0xd4, 0x54, 0x75, 0xe5, 0x0c, 0x93, 0x76, 0xc7, 0xa5, + 0x10, 0x79, 0xb9, 0x40, 0xcc, 0xfe, 0xfb, 0xbe, 0xe2, 0x03, 0x2a, 0x47, 0x35, 0x28, 0xbd, 0xe8, + 0xe1, 0x1e, 0x56, 0x2c, 0x95, 0xd8, 0x8e, 0x62, 0xfb, 0x93, 0x88, 0xfd, 0xa9, 0xcf, 0xcb, 0x45, + 0xaa, 0x6c, 0x7a, 0x3a, 0x99, 0xab, 0xd0, 0x1e, 0xac, 0x12, 0xc3, 0xc5, 0xb6, 0xdd, 0xb3, 0x5c, + 0xc5, 0x6b, 0x0f, 0x5b, 0x75, 0x89, 0x69, 0xd0, 0xae, 0xc8, 0xcb, 0xc5, 0x40, 0x77, 0x14, 0xa8, + 0x44, 0x1d, 0xa0, 0x61, 0x38, 0x3c, 0xc5, 0x9b, 0xb0, 0xec, 0x60, 0xbb, 0xef, 0x91, 0x91, 0xa5, + 0x78, 0xbb, 0xe7, 0x94, 0x05, 0xba, 0xa5, 0x79, 0x5f, 0x2c, 0x59, 0xfb, 0x9e, 0x10, 0xad, 0xc1, + 0xbc, 0x6e, 0x76, 0x55, 0x62, 0xf8, 0x1c, 0x24, 0xb3, 0x5f, 0x9e, 0xdc, 0xc1, 0xaa, 0xad, 0x75, + 0xca, 0x33, 0xd4, 0x8d, 0xfd, 0x12, 0x5b, 0x7c, 0x1b, 0x78, 0x65, 0x38, 0xb1, 0x24, 0x32, 0xaf, + 0xf0, 0xc6, 0xcc, 0xfb, 0xad, 0x00, 0x85, 0xb8, 0x19, 0x3a, 0x84, 0x45, 0x8f, 0x7d, 0xb8, 0x31, + 0x83, 0xbf, 0x36, 0x32, 0x04, 0x9a, 0x11, 0x77, 0x3d, 0x4c, 0xc9, 0x1e, 0x71, 0x71, 0x31, 0x3a, + 0x81, 0x02, 0x27, 0x9e, 0x00, 0xcd, 0x67, 0x9e, 0xad, 0x31, 0xcc, 0x93, 0x80, 0xc8, 0xb9, 0x8b, + 0xab, 0x22, 0xdc, 0xf3, 0x85, 0x00, 0xc5, 0x50, 0x22, 0x41, 0xbb, 0xfe, 0x13, 0xb2, 0x5e, 0xc3, + 0x39, 0x96, 0xaa, 0xf1, 0xae, 0x1b, 0x0a, 0x58, 0x33, 0xa7, 0xe3, 0xcd, 0x8c, 0xee, 0x0f, 0x91, + 0x69, 0x0f, 0x4c, 0xb7, 0x6a, 0x79, 0x98, 0xce, 0x77, 0xe9, 0x48, 0x3a, 0x13, 0x59, 0xad, 0x0a, + 0xd0, 0x55, 0x35, 0x36, 0xf2, 0xac, 0x2f, 0x42, 0x12, 0x6f, 0x09, 0x01, 0x5f, 0x30, 0x9e, 0x1a, + 0x0a, 0xd0, 0xff, 0x61, 0x2d, 0xce, 0x26, 0xa7, 0xd8, 0x68, 0xbb, 0x1d, 0xca, 0x59, 0x79, 0xb9, + 0x44, 0xa2, 0x6c, 0xe2, 0x2b, 0xd1, 0x55, 0x58, 0x64, 0x47, 0x80, 0x3f, 0x90, 0x73, 0x14, 0x37, + 0xc7, 0x64, 0x94, 0x00, 0xef, 0x43, 0xc6, 0xf2, 0xe6, 0x90, 0x60, 0x87, 0xf2, 0xcd, 0xe4, 0x22, + 0x34, 0x99, 0xa9, 0x1c, 0x38, 0xa1, 0xb7, 0x21, 0xa7, 0x1b, 0x0e, 0x6f, 0xcf, 0xf2, 0x02, 0xc5, + 0xa8, 0xc4, 0x31, 0x86, 0x53, 0x24, 0x83, 0x1e, 0xfc, 0xef, 0x5d, 0x75, 0x8a, 0x09, 0xf0, 0x08, + 0xc3, 0xba, 0xc7, 0x1a, 0x5e, 0xd2, 0x0a, 0x8d, 0x74, 0x1e, 0x04, 0xf0, 0xfb, 0xf3, 0x3f, 0xf1, + 0x00, 0x13, 0x69, 0x49, 0x2e, 0x71, 0xb4, 0x28, 0xe7, 0x3c, 0x85, 0xa2, 0xc7, 0x39, 0xd1, 0x08, + 0x0e, 0x6b, 0xda, 0xed, 0x78, 0x88, 0x71, 0xd4, 0x25, 0xaf, 0x10, 0xb3, 0x1f, 0x91, 0x38, 0x0f, + 0x67, 0x33, 0x33, 0x85, 0x8c, 0xf8, 0x63, 0x1a, 0xd6, 0xc7, 0xb4, 0xfa, 0x5f, 0xaa, 0x49, 0xa6, + 0x3d, 0x9b, 0x50, 0x13, 0x90, 0xa5, 0x11, 0x45, 0xc7, 0x7d, 0xa2, 0x61, 0x45, 0xc7, 0xae, 0x4a, + 0x4e, 0x1d, 0xd6, 0x13, 0x23, 0x8c, 0xd5, 0xac, 0x4b, 0x0d, 0x6a, 0xd8, 0xf0, 0xed, 0x0e, 0x53, + 0x72, 0xc1, 0xd2, 0x48, 0x44, 0x76, 0x50, 0x80, 0xa5, 0x28, 0x9a, 0xe8, 0x40, 0x21, 0xee, 0x89, + 0xfe, 0x05, 0x59, 0x66, 0x15, 0x1c, 0x5b, 0x8b, 0x83, 0x8b, 0x8d, 0x8c, 0x6f, 0x25, 0x35, 0xe4, + 0x8c, 0xaf, 0x96, 0x74, 0xf4, 0x3f, 0x58, 0xeb, 0x13, 0xdb, 0xed, 0xa9, 0xa7, 0x4a, 0xab, 0x67, + 0x68, 0x1e, 0xcd, 0x2b, 0xc4, 0xd0, 0xf1, 0x4b, 0x76, 0x74, 0xac, 0x32, 0xed, 0x7b, 0x4c, 0x29, + 0x79, 0x3a, 0xf1, 0xbf, 0xb0, 0x16, 0xa7, 0xe8, 0x4b, 0x8e, 0xca, 0x4f, 0x00, 0xed, 0xeb, 0x7a, + 0x9c, 0xd1, 0x93, 0x76, 0xbd, 0x06, 0x8b, 0x01, 0x59, 0x0d, 0x2f, 0xae, 0xf4, 0x9a, 0x7b, 0xcc, + 0xe5, 0xde, 0x35, 0x37, 0x30, 0x92, 0x74, 0xb1, 0x04, 0xc5, 0x08, 0x3a, 0xbb, 0xa1, 0xfe, 0x1b, + 0x4a, 0xfe, 0xb5, 0x75, 0x8a, 0xb8, 0x62, 0x19, 0xd6, 0xe2, 0xc6, 0x0c, 0xe6, 0x16, 0xac, 0xb2, + 0xdb, 0x6f, 0xf4, 0xa2, 0x9b, 0x84, 0xb2, 0xce, 0x43, 0xc6, 0xee, 0x10, 0xe2, 0x36, 0xa0, 0x07, + 0xd8, 0x9d, 0x26, 0x91, 0xcf, 0x05, 0x28, 0x46, 0x4c, 0xff, 0x10, 0xad, 0xdf, 0x83, 0x4c, 0x70, + 0xfc, 0xcc, 0x4c, 0x79, 0x56, 0x06, 0x1e, 0xe2, 0x16, 0xac, 0x3c, 0xc0, 0xee, 0x14, 0xeb, 0x56, + 0xe8, 0xf2, 0xa6, 0xbc, 0x38, 0x85, 0x9f, 0x08, 0xe9, 0x29, 0x9f, 0x08, 0xa5, 0x48, 0x51, 0xf8, + 0x3d, 0x46, 0xfc, 0x10, 0x56, 0xa3, 0x62, 0x16, 0x79, 0x1f, 0xb2, 0x7c, 0x11, 0xfe, 0xb5, 0x24, + 0x81, 0xc9, 0x13, 0x8a, 0x2c, 0x0f, 0xbd, 0xc4, 0xd5, 0xf0, 0x92, 0x82, 0x80, 0x4f, 0x68, 0x1e, + 0x43, 0x29, 0x8b, 0xf7, 0x0e, 0x64, 0x58, 0xa6, 0x3c, 0x9c, 0x98, 0x10, 0x2e, 0x56, 0x1f, 0x39, + 0xf0, 0xa9, 0x7d, 0x9f, 0x01, 0xc4, 0xb4, 0x75, 0xfa, 0x01, 0x80, 0xd2, 0x24, 0x92, 0x60, 0xde, + 0x7f, 0x0e, 0xa3, 0x2b, 0x71, 0xb8, 0xc8, 0x17, 0x80, 0x4a, 0x75, 0x9c, 0x9a, 0xb5, 0x5f, 0x0a, + 0xc9, 0x90, 0x0d, 0x1e, 0x9a, 0x68, 0xa4, 0x07, 0xe2, 0x2f, 0xec, 0xca, 0xd5, 0x09, 0x16, 0x61, + 0xcc, 0xe0, 0x5d, 0x38, 0x8a, 0x19, 0x7f, 0xc1, 0x8e, 0x62, 0x8e, 0x3e, 0x2a, 0x53, 0xe8, 0x53, + 0xc8, 0x47, 0x6e, 0xe1, 0xe8, 0x7a, 0xdc, 0x2b, 0xe9, 0xd5, 0x59, 0xb9, 0x71, 0x89, 0x55, 0x80, + 0xaf, 0xc2, 0x52, 0x94, 0xbb, 0xd0, 0x18, 0xd7, 0xd8, 0xac, 0x56, 0x6e, 0x5e, 0x66, 0x16, 0x84, + 0x78, 0x0a, 0xb9, 0x10, 0x1d, 0x21, 0x31, 0x61, 0x6f, 0xe2, 0xe0, 0xd7, 0x26, 0xda, 0x84, 0x93, + 0x8f, 0x92, 0xd4, 0x68, 0xf2, 0x89, 0x8c, 0x37, 0x9a, 0xfc, 0x18, 0xae, 0xa3, 0xf5, 0x8f, 0x30, + 0xd8, 0x68, 0xfd, 0x93, 0xc8, 0xb0, 0x72, 0xe3, 0x12, 0xab, 0x70, 0x71, 0x42, 0x83, 0x87, 0xc4, + 0x89, 0x53, 0x39, 0xa6, 0x38, 0x09, 0x93, 0x2b, 0xa6, 0xd0, 0x13, 0x80, 0xe1, 0x8c, 0xa1, 0xab, + 0x93, 0xe6, 0xcf, 0xc7, 0x9d, 0x62, 0x44, 0xc5, 0x14, 0xfa, 0x18, 0x16, 0xc3, 0x14, 0x83, 0x26, + 0x65, 0xc3, 0x69, 0xa2, 0x72, 0x7d, 0xb2, 0x51, 0xac, 0x1a, 0x9c, 0x4e, 0xd0, 0x84, 0x8c, 0x9c, + 0x49, 0xd5, 0x88, 0xf3, 0x91, 0x98, 0x3a, 0x78, 0xf4, 0xea, 0x75, 0x35, 0xf5, 0xd3, 0xeb, 0x6a, + 0xea, 0xb3, 0x41, 0x55, 0x78, 0x35, 0xa8, 0x0a, 0x3f, 0x0c, 0xaa, 0xc2, 0xaf, 0x83, 0xaa, 0xf0, + 0xd1, 0x5b, 0x6f, 0xf8, 0x85, 0xf2, 0xd9, 0x3c, 0xfd, 0x1e, 0x79, 0xe7, 0xb7, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x56, 0xff, 0x9c, 0xcb, 0xe3, 0x14, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.proto b/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.proto index cd3c8b6b1c..3ba0257cba 100644 --- a/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.proto +++ b/pkg/ncproxy/ncproxygrpc/v1/networkconfigproxy.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package ncproxygrpc.v1; -option go_package = "v1"; +option go_package = "github.com/Microsoft/hcsshim/pkg/ncproxy/ncproxygrpc/v1"; service NetworkConfigProxy { rpc AddNIC(AddNICRequest) returns (AddNICResponse) {} diff --git a/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.pb.go b/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.pb.go index 842300c922..c4d27fa792 100644 --- a/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.pb.go +++ b/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.pb.go @@ -470,51 +470,51 @@ func init() { } var fileDescriptor_912a3d4a179bd2c2 = []byte{ - // 701 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4b, 0x53, 0x13, 0x4d, - 0x14, 0xa5, 0xc3, 0xeb, 0xe3, 0x26, 0x3c, 0xaa, 0x79, 0x7c, 0x31, 0x42, 0xc0, 0xc1, 0xd2, 0x88, - 0x98, 0x48, 0xd8, 0xb8, 0x71, 0x21, 0x50, 0x05, 0xa9, 0x02, 0x0a, 0x07, 0x56, 0x6e, 0x52, 0xc3, - 0x4c, 0x67, 0xd2, 0x45, 0xd2, 0xdd, 0x76, 0x77, 0x02, 0xd9, 0xb9, 0x73, 0x63, 0x95, 0x7f, 0x8b, - 0xa5, 0x4b, 0x57, 0x28, 0xf9, 0x13, 0x6e, 0x2d, 0xe6, 0x11, 0xe2, 0x24, 0x93, 0x0a, 0x96, 0xbb, - 0xf4, 0xed, 0x7b, 0xcf, 0x3d, 0xe7, 0xdc, 0xe9, 0x1b, 0xd8, 0x77, 0xa9, 0xae, 0x36, 0xce, 0xf3, + // 703 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4b, 0x4f, 0x1b, 0x3b, + 0x14, 0xc6, 0xe1, 0x75, 0x39, 0x09, 0x0f, 0x99, 0xc7, 0xcd, 0xcd, 0x85, 0xc0, 0x1d, 0xae, 0xda, + 0x94, 0xd2, 0xa4, 0x04, 0xa9, 0xea, 0xa6, 0x8b, 0x02, 0x12, 0x44, 0x02, 0x44, 0x07, 0x56, 0xdd, + 0x44, 0xc3, 0x8c, 0x33, 0xb1, 0x48, 0x6c, 0xd7, 0x76, 0x02, 0xd9, 0x75, 0xd7, 0x4d, 0xa5, 0xfe, + 0x2d, 0x96, 0x5d, 0x76, 0x45, 0x4b, 0xfe, 0x44, 0xb7, 0x15, 0xf3, 0x08, 0xe9, 0x24, 0x13, 0x05, + 0xd4, 0xdd, 0xf8, 0x3c, 0xbf, 0xef, 0x3b, 0xe3, 0x63, 0xd8, 0x77, 0xa9, 0xae, 0x36, 0xce, 0xf3, 0x36, 0xaf, 0x17, 0x8e, 0xa8, 0x2d, 0xb9, 0xe2, 0x15, 0x5d, 0xa8, 0xda, 0x4a, 0x55, 0x69, 0xbd, 0x20, 0x2e, 0xdc, 0x02, 0xb3, 0x85, 0xe4, 0x57, 0xad, 0x02, 0xe3, 0x0e, 0x61, 0x44, 0xab, 0xa6, - 0x5d, 0x68, 0x6e, 0x75, 0x9d, 0xf2, 0x42, 0x72, 0xcd, 0xf1, 0x74, 0x57, 0xa4, 0xb9, 0x65, 0x7c, - 0x45, 0x90, 0xd9, 0xe5, 0xac, 0x42, 0xdd, 0x86, 0x24, 0xc7, 0x44, 0x5f, 0x72, 0x79, 0x41, 0x99, - 0x6b, 0x92, 0x8f, 0x0d, 0xa2, 0x34, 0x2e, 0x42, 0xca, 0xe6, 0x4c, 0x5b, 0x94, 0x11, 0x59, 0xa6, - 0x4e, 0x1a, 0xad, 0xa1, 0xdc, 0xd4, 0xce, 0x6c, 0xfb, 0x66, 0x35, 0xb9, 0x1b, 0xc6, 0x4b, 0x7b, - 0x66, 0xb2, 0x93, 0x54, 0x72, 0xf0, 0x5b, 0x48, 0x49, 0xbf, 0xbc, 0xac, 0x5b, 0x82, 0xa4, 0x13, - 0x6b, 0x28, 0x37, 0x53, 0xcc, 0xe4, 0xff, 0x68, 0x9c, 0x0f, 0x3a, 0x9c, 0xb5, 0x04, 0x31, 0x93, - 0xf2, 0xfe, 0x60, 0xac, 0xc0, 0xe3, 0xbe, 0x84, 0x94, 0xe0, 0x4c, 0x11, 0xe3, 0x3d, 0xac, 0x9c, - 0x50, 0xe6, 0x1e, 0x73, 0x27, 0xbc, 0x3d, 0x25, 0xb2, 0x49, 0x6d, 0x12, 0x52, 0x7e, 0x0d, 0x0b, - 0x82, 0x32, 0xb7, 0x1c, 0x72, 0xa8, 0x13, 0xa5, 0x2c, 0x97, 0xf8, 0xd4, 0x4d, 0x2c, 0xee, 0xd5, - 0x1d, 0xf9, 0x37, 0xc6, 0x19, 0x64, 0xe3, 0x20, 0xfd, 0xa6, 0xb8, 0x08, 0x8b, 0x01, 0xa6, 0x1f, - 0x88, 0x80, 0xce, 0x8b, 0x2e, 0x86, 0x21, 0x6a, 0x1b, 0xc1, 0x7a, 0x47, 0x48, 0xc7, 0xac, 0x5e, - 0x8b, 0xa3, 0x76, 0xa1, 0x07, 0xd9, 0xd5, 0x33, 0xa1, 0xc4, 0x10, 0x13, 0x3a, 0x80, 0x05, 0xe6, - 0xf3, 0x28, 0x33, 0xab, 0x4e, 0x94, 0xb0, 0x6c, 0x72, 0x57, 0x3b, 0xea, 0xd5, 0x2e, 0xb5, 0x6f, - 0x56, 0x71, 0xc0, 0xf3, 0x38, 0xbc, 0x2e, 0xed, 0x99, 0x98, 0x45, 0x63, 0x8e, 0x21, 0xe0, 0xe9, - 0x60, 0x8d, 0x81, 0x81, 0x07, 0x00, 0x94, 0x69, 0x22, 0x2b, 0x96, 0x4d, 0x54, 0x1a, 0xad, 0x8d, - 0xe6, 0x92, 0xc5, 0x5c, 0x44, 0x62, 0xb4, 0xbe, 0x14, 0x16, 0x98, 0x5d, 0xb5, 0xc6, 0x17, 0x04, - 0xf8, 0x5e, 0xd8, 0xc9, 0x3b, 0xc7, 0x91, 0x44, 0x29, 0x9c, 0x86, 0xc9, 0x26, 0x91, 0x8a, 0x72, - 0x16, 0xcc, 0x24, 0x3c, 0xe2, 0x19, 0x48, 0x50, 0xe1, 0x4b, 0x33, 0x13, 0x54, 0xe0, 0x75, 0x98, - 0x16, 0x92, 0x54, 0xe8, 0x55, 0xb9, 0x46, 0x98, 0xab, 0xab, 0xe9, 0x31, 0xef, 0x2a, 0xe5, 0x07, - 0x0f, 0xbd, 0x18, 0x7e, 0x0e, 0xb3, 0x0e, 0xa9, 0x58, 0x8d, 0x9a, 0x2e, 0xbb, 0x96, 0x26, 0x97, - 0x56, 0x2b, 0x3d, 0xee, 0xa5, 0xcd, 0x04, 0xe1, 0x7d, 0x3f, 0x6a, 0xfc, 0x40, 0xf0, 0x28, 0x96, - 0x38, 0xc6, 0x30, 0x76, 0x67, 0x70, 0x40, 0xc9, 0xfb, 0x8d, 0x57, 0x21, 0x59, 0xb7, 0xec, 0xb2, - 0xe5, 0x13, 0xf7, 0xe7, 0x65, 0x42, 0xdd, 0xb2, 0x43, 0x29, 0xff, 0x6c, 0x3a, 0x78, 0x17, 0x92, - 0x54, 0x04, 0x8d, 0x88, 0x4a, 0x8f, 0x79, 0xb6, 0x3f, 0x89, 0xb3, 0xbd, 0x63, 0xa6, 0xd9, 0x5d, - 0x65, 0x98, 0xb0, 0xbc, 0x4f, 0xf4, 0x01, 0x57, 0xfa, 0x90, 0xdb, 0x56, 0xad, 0x24, 0xc2, 0xac, - 0xbf, 0x5f, 0x11, 0xc6, 0x1b, 0x58, 0x89, 0xc1, 0x0c, 0xbe, 0x97, 0xff, 0x61, 0x92, 0x0a, 0xcf, - 0xa3, 0xc0, 0xbb, 0x09, 0xea, 0xe5, 0x6c, 0x3c, 0x83, 0x64, 0xd7, 0x53, 0xc0, 0x53, 0x30, 0x7e, - 0x4a, 0x74, 0x43, 0xcc, 0x8d, 0xe0, 0x14, 0xfc, 0x77, 0x46, 0x2c, 0xe9, 0xf0, 0x4b, 0x36, 0x87, - 0x8a, 0xbf, 0x46, 0x01, 0xf7, 0x3e, 0x68, 0x5c, 0x83, 0xf9, 0x3e, 0xcb, 0x05, 0xbf, 0xe8, 0xf5, - 0x24, 0x66, 0x23, 0x66, 0x36, 0x86, 0x49, 0x0d, 0x54, 0x7c, 0x46, 0xb0, 0x3c, 0xe8, 0x79, 0xe0, - 0x62, 0x1c, 0x58, 0xfc, 0xbe, 0xc8, 0x6c, 0x3f, 0xa8, 0x26, 0x60, 0xd2, 0x80, 0xa5, 0xfe, 0x2b, - 0x0e, 0x6f, 0x46, 0xe0, 0x06, 0x2e, 0xd7, 0xcc, 0xab, 0x21, 0xb3, 0x83, 0xb6, 0x12, 0x16, 0xfb, - 0xce, 0x19, 0xbf, 0x8c, 0xe0, 0x0c, 0xfa, 0xc2, 0x32, 0x9b, 0xc3, 0x25, 0xfb, 0x3d, 0x77, 0x96, - 0xaf, 0x6f, 0xb3, 0x23, 0xdf, 0x6f, 0xb3, 0x23, 0x9f, 0xda, 0x59, 0x74, 0xdd, 0xce, 0xa2, 0x6f, - 0xed, 0x2c, 0xfa, 0xd9, 0xce, 0xa2, 0x0f, 0x89, 0xe6, 0xd6, 0xf9, 0x84, 0xf7, 0x2f, 0xb8, 0xfd, - 0x3b, 0x00, 0x00, 0xff, 0xff, 0x96, 0xb0, 0x6a, 0x8e, 0x50, 0x07, 0x00, 0x00, + 0x5d, 0x68, 0x6e, 0x75, 0x9d, 0xf2, 0x42, 0x72, 0xcd, 0xf1, 0x74, 0x97, 0xa5, 0xb9, 0x65, 0x7c, + 0x41, 0x90, 0xd9, 0xe5, 0xac, 0x42, 0xdd, 0x86, 0x24, 0xc7, 0x44, 0x5f, 0x72, 0x79, 0x41, 0x99, + 0x6b, 0x92, 0x0f, 0x0d, 0xa2, 0x34, 0x2e, 0x42, 0xca, 0xe6, 0x4c, 0x5b, 0x94, 0x11, 0x59, 0xa6, + 0x4e, 0x1a, 0xad, 0xa1, 0xdc, 0xd4, 0xce, 0x6c, 0xfb, 0x66, 0x35, 0xb9, 0x1b, 0xda, 0x4b, 0x7b, + 0x66, 0xb2, 0x13, 0x54, 0x72, 0xf0, 0x1b, 0x48, 0x49, 0x3f, 0xbd, 0xac, 0x5b, 0x82, 0xa4, 0x13, + 0x6b, 0x28, 0x37, 0x53, 0xcc, 0xe4, 0x7f, 0x6b, 0x9c, 0x0f, 0x3a, 0x9c, 0xb5, 0x04, 0x31, 0x93, + 0xf2, 0xfe, 0x60, 0xac, 0xc0, 0xbf, 0x7d, 0x01, 0x29, 0xc1, 0x99, 0x22, 0xc6, 0x3b, 0x58, 0x39, + 0xa1, 0xcc, 0x3d, 0xe6, 0x4e, 0xe8, 0x3d, 0x25, 0xb2, 0x49, 0x6d, 0x12, 0x42, 0x7e, 0x09, 0x0b, + 0x82, 0x32, 0xb7, 0x1c, 0x62, 0xa8, 0x13, 0xa5, 0x2c, 0x97, 0xf8, 0xd0, 0x4d, 0x2c, 0xee, 0xd9, + 0x1d, 0xf9, 0x1e, 0xe3, 0x0c, 0xb2, 0x71, 0x25, 0xfd, 0xa6, 0xb8, 0x08, 0x8b, 0x41, 0x4d, 0xdf, + 0x10, 0x29, 0x3a, 0x2f, 0xba, 0x10, 0x86, 0x55, 0xdb, 0x08, 0xd6, 0x3b, 0x44, 0x3a, 0x62, 0xf5, + 0x4a, 0x1c, 0x95, 0x0b, 0x3d, 0x48, 0xae, 0x9e, 0x09, 0x25, 0x86, 0x98, 0xd0, 0x01, 0x2c, 0x30, + 0x1f, 0x47, 0x99, 0x59, 0x75, 0xa2, 0x84, 0x65, 0x93, 0xbb, 0xdc, 0x51, 0x2f, 0x77, 0xa9, 0x7d, + 0xb3, 0x8a, 0x03, 0x9c, 0xc7, 0xa1, 0xbb, 0xb4, 0x67, 0x62, 0x16, 0xb5, 0x39, 0x86, 0x80, 0xff, + 0x07, 0x73, 0x0c, 0x04, 0x3c, 0x00, 0xa0, 0x4c, 0x13, 0x59, 0xb1, 0x6c, 0xa2, 0xd2, 0x68, 0x6d, + 0x34, 0x97, 0x2c, 0xe6, 0x22, 0x14, 0xa3, 0xf9, 0xa5, 0x30, 0xc1, 0xec, 0xca, 0x35, 0x3e, 0x23, + 0xc0, 0xf7, 0xc4, 0x4e, 0xde, 0x3a, 0x8e, 0x24, 0x4a, 0xe1, 0x34, 0x4c, 0x36, 0x89, 0x54, 0x94, + 0xb3, 0x60, 0x26, 0xe1, 0x11, 0xcf, 0x40, 0x82, 0x0a, 0x9f, 0x9a, 0x99, 0xa0, 0x02, 0xaf, 0xc3, + 0xb4, 0x90, 0xa4, 0x42, 0xaf, 0xca, 0x35, 0xc2, 0x5c, 0x5d, 0x4d, 0x8f, 0x79, 0xae, 0x94, 0x6f, + 0x3c, 0xf4, 0x6c, 0xf8, 0x29, 0xcc, 0x3a, 0xa4, 0x62, 0x35, 0x6a, 0xba, 0xec, 0x5a, 0x9a, 0x5c, + 0x5a, 0xad, 0xf4, 0xb8, 0x17, 0x36, 0x13, 0x98, 0xf7, 0x7d, 0xab, 0xf1, 0x1d, 0xc1, 0x3f, 0xb1, + 0xc0, 0x31, 0x86, 0xb1, 0x3b, 0x81, 0x03, 0x48, 0xde, 0x37, 0x5e, 0x85, 0x64, 0xdd, 0xb2, 0xcb, + 0x96, 0x0f, 0xdc, 0x9f, 0x97, 0x09, 0x75, 0xcb, 0x0e, 0xa9, 0xfc, 0xb1, 0xe9, 0xe0, 0x5d, 0x48, + 0x52, 0x11, 0x34, 0x22, 0x2a, 0x3d, 0xe6, 0xc9, 0xfe, 0x5f, 0x9c, 0xec, 0x1d, 0x31, 0xcd, 0xee, + 0x2c, 0xc3, 0x84, 0xe5, 0x7d, 0xa2, 0x0f, 0xb8, 0xd2, 0x87, 0xdc, 0xb6, 0x6a, 0x25, 0x11, 0x46, + 0x3d, 0x7e, 0x45, 0x18, 0xaf, 0x61, 0x25, 0xa6, 0x66, 0xf0, 0xbf, 0xfc, 0x0d, 0x93, 0x54, 0x78, + 0x1a, 0x05, 0xda, 0x4d, 0x50, 0x2f, 0x66, 0xe3, 0x09, 0x24, 0xbb, 0xae, 0x02, 0x9e, 0x82, 0xf1, + 0x53, 0xa2, 0x1b, 0x62, 0x6e, 0x04, 0xa7, 0xe0, 0xaf, 0x33, 0x62, 0x49, 0x87, 0x5f, 0xb2, 0x39, + 0x54, 0xfc, 0x39, 0x0a, 0xb8, 0xf7, 0x42, 0xe3, 0x1a, 0xcc, 0xf7, 0x59, 0x2e, 0xf8, 0x59, 0xaf, + 0x26, 0x31, 0x1b, 0x31, 0xb3, 0x31, 0x4c, 0x68, 0xc0, 0xe2, 0x13, 0x82, 0xe5, 0x41, 0xd7, 0x03, + 0x17, 0xe3, 0x8a, 0xc5, 0xef, 0x8b, 0xcc, 0xf6, 0x83, 0x72, 0x02, 0x24, 0x0d, 0x58, 0xea, 0xbf, + 0xe2, 0xf0, 0x66, 0xa4, 0xdc, 0xc0, 0xe5, 0x9a, 0x79, 0x31, 0x64, 0x74, 0xd0, 0x56, 0xc2, 0x62, + 0xdf, 0x39, 0xe3, 0xe7, 0x91, 0x3a, 0x83, 0xfe, 0xb0, 0xcc, 0xe6, 0x70, 0xc1, 0x7e, 0xcf, 0x9d, + 0x93, 0xeb, 0xdb, 0xec, 0xc8, 0xb7, 0xdb, 0xec, 0xc8, 0xc7, 0x76, 0x16, 0x5d, 0xb7, 0xb3, 0xe8, + 0x6b, 0x3b, 0x8b, 0x7e, 0xb4, 0xb3, 0xe8, 0xfd, 0xab, 0xc7, 0xbd, 0xa1, 0xe7, 0x13, 0xde, 0xcb, + 0xb9, 0xfd, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x82, 0x7f, 0x15, 0x14, 0x84, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.proto b/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.proto index ba33f52424..5fc7400b84 100644 --- a/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.proto +++ b/pkg/ncproxy/nodenetsvc/v1/nodenetsvc.proto @@ -1,6 +1,6 @@ syntax = "proto3"; package nodenetsvc.v1; -option go_package = "v1"; +option go_package = "github.com/Microsoft/hcsshim/pkg/ncproxy/nodenetsvc/v1"; service NodeNetworkService { rpc ConfigureNetworking(ConfigureNetworkingRequest) returns (ConfigureNetworkingResponse); From 13ceffd8e8ae1764ea0b0c526d371764b91e9b2a Mon Sep 17 00:00:00 2001 From: Maksim An Date: Wed, 13 Apr 2022 12:51:38 -0700 Subject: [PATCH 02/16] Add guest package for fetching attestation report via syscall (#1341) Add `internal/guest/linux package`, which contains linux ioctl definitions. Devicemapper code is refactored to use the new package. Introduce new `amdsevsnp` package with Introduce ioctl wrappers and structs required to fetch attestation report. Validate that `LaunchData` provided to HCS during UVM boot and `HostData` returned as part of attestation report match. Add utility binary to fetch SNP report and update Makefile to support `DEV_BUILD` parameter, which includes test utilities inside LCOW image. Fake attestation report can be used when testing integrations. Signed-off-by: Maksim An --- Makefile | 21 +- internal/guest/linux/ioctl.go | 49 ++++ internal/guest/runtime/hcsv2/hostdata.go | 34 +++ internal/guest/runtime/hcsv2/uvm.go | 9 + .../storage/devicemapper/devicemapper.go | 38 +-- internal/tools/snp-report/fake/report.go | 57 +++++ internal/tools/snp-report/main.go | 117 +++++++++ internal/uvm/create_lcow.go | 23 +- pkg/amdsevsnp/report.go | 234 ++++++++++++++++++ pkg/amdsevsnp/report_test.go | 53 ++++ pkg/securitypolicy/securitypolicy.go | 15 ++ .../github.com/Microsoft/hcsshim/Makefile | 21 +- .../hcsshim/internal/uvm/create_lcow.go | 23 +- .../pkg/securitypolicy/securitypolicy.go | 15 ++ 14 files changed, 651 insertions(+), 58 deletions(-) create mode 100644 internal/guest/linux/ioctl.go create mode 100644 internal/guest/runtime/hcsv2/hostdata.go create mode 100644 internal/tools/snp-report/fake/report.go create mode 100644 internal/tools/snp-report/main.go create mode 100644 pkg/amdsevsnp/report.go create mode 100644 pkg/amdsevsnp/report_test.go diff --git a/Makefile b/Makefile index e1a0f51129..c11786ce3a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ BASE:=base.tar.gz +DEV_BUILD:=0 GO:=go GO_FLAGS:=-ldflags "-s -w" # strip Go binaries @@ -16,6 +17,12 @@ GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA) SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +DELTA_TARGET=out/delta.tar.gz + +ifeq "$(DEV_BUILD)" "1" +DELTA_TARGET=out/delta-dev.tar.gz +endif + # The link aliases for gcstools GCS_TOOLS=\ generichook \ @@ -55,6 +62,15 @@ out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools bin/cmd/ho tar -zcf $@ -C rootfs . rm -rf rootfs +# This target includes utilities which may be useful for testing purposes. +out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report + rm -rf rootfs-dev + mkdir rootfs-dev + tar -xzf out/delta.tar.gz -C rootfs-dev + cp bin/internal/tools/snp-report rootfs-dev/bin/ + tar -zcf $@ -C rootfs-dev . + rm -rf rootfs-dev + out/rootfs.tar.gz: out/initrd.img rm -rf rootfs-conv mkdir rootfs-conv @@ -62,8 +78,8 @@ out/rootfs.tar.gz: out/initrd.img tar -zcf $@ -C rootfs-conv . rm -rf rootfs-conv -out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh - $(SRCROOT)/hack/catcpio.sh "$(BASE)" out/delta.tar.gz > out/initrd.img.uncompressed +out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh + $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed gzip -c out/initrd.img.uncompressed > $@ rm out/initrd.img.uncompressed @@ -71,6 +87,7 @@ out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh -include deps/cmd/gcstools.gomake -include deps/cmd/hooks/wait-paths.gomake -include deps/cmd/tar2ext4.gomake +-include deps/internal/tools/snp-report.gomake # Implicit rule for includes that define Go targets. %.gomake: $(SRCROOT)/Makefile diff --git a/internal/guest/linux/ioctl.go b/internal/guest/linux/ioctl.go new file mode 100644 index 0000000000..c87a3da9f5 --- /dev/null +++ b/internal/guest/linux/ioctl.go @@ -0,0 +1,49 @@ +//go:build linux +// +build linux + +// Package linux contains definitions required for making a linux ioctl. +package linux + +import ( + "os" + "unsafe" + + "golang.org/x/sys/unix" +) + +// 32 bits to describe an ioctl: +// 0-7: NR (command for a given ioctl type) +// 8-15: TYPE (ioctl type) +// 16-29: SIZE (payload size) +// 30-31: DIR (direction of ioctl, can be: none/write/read/write-read) +const ( + IocWrite = 1 + IocRead = 2 + IocNRBits = 8 + IocTypeBits = 8 + IocSizeBits = 14 + IocDirBits = 2 + + IocNRMask = (1 << IocNRBits) - 1 + IocTypeMask = (1 << IocTypeBits) - 1 + IocSizeMask = (1 << IocSizeBits) - 1 + IocDirMask = (1 << IocDirBits) - 1 + IocTypeShift = IocNRBits + IocSizeShift = IocTypeShift + IocTypeBits + IocDirShift = IocSizeShift + IocSizeBits + IocWRBase = (IocRead | IocWrite) << IocDirShift +) + +// Ioctl makes a syscall described by `command` with data `dataPtr` to device +// driver file `f`. +func Ioctl(f *os.File, command int, dataPtr unsafe.Pointer) error { + if _, _, err := unix.Syscall( + unix.SYS_IOCTL, + f.Fd(), + uintptr(command), + uintptr(dataPtr), + ); err != 0 { + return err + } + return nil +} diff --git a/internal/guest/runtime/hcsv2/hostdata.go b/internal/guest/runtime/hcsv2/hostdata.go new file mode 100644 index 0000000000..562ed006e1 --- /dev/null +++ b/internal/guest/runtime/hcsv2/hostdata.go @@ -0,0 +1,34 @@ +//go:build linux +// +build linux + +package hcsv2 + +import ( + "bytes" + "fmt" + "os" + + "github.com/Microsoft/hcsshim/pkg/amdsevsnp" +) + +// validateHostData fetches SNP report (if applicable) and validates `hostData` against +// HostData set at UVM launch. +func validateHostData(hostData []byte) error { + report, err := amdsevsnp.FetchParsedSNPReport(nil) + if err != nil { + // For non-SNP hardware /dev/sev will not exist + if os.IsNotExist(err) { + return nil + } + return err + } + + if bytes.Compare(hostData, report.HostData) != 0 { + return fmt.Errorf( + "security policy digest %q doesn't match HostData provided at launch %q", + hostData, + report.HostData, + ) + } + return nil +} diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index d5e004fc6a..b1abecc811 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -95,6 +95,15 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error { return err } + hostData, err := securitypolicy.NewSecurityPolicyDigest(base64Policy) + if err != nil { + return err + } + + if err := validateHostData(hostData[:]); err != nil { + return err + } + h.securityPolicyEnforcer = p h.securityPolicyEnforcerSet = true diff --git a/internal/guest/storage/devicemapper/devicemapper.go b/internal/guest/storage/devicemapper/devicemapper.go index 3b313284c1..3f9b6797e7 100644 --- a/internal/guest/storage/devicemapper/devicemapper.go +++ b/internal/guest/storage/devicemapper/devicemapper.go @@ -12,6 +12,8 @@ import ( "unsafe" "golang.org/x/sys/unix" + + "github.com/Microsoft/hcsshim/internal/guest/linux" ) // CreateFlags modify the operation of CreateDevice @@ -28,24 +30,9 @@ var ( ) const ( - _IOC_WRITE = 1 - _IOC_READ = 2 - _IOC_NRBITS = 8 - _IOC_TYPEBITS = 8 - _IOC_SIZEBITS = 14 - _IOC_DIRBITS = 2 - - _IOC_NRMASK = ((1 << _IOC_NRBITS) - 1) - _IOC_TYPEMASK = ((1 << _IOC_TYPEBITS) - 1) - _IOC_SIZEMASK = ((1 << _IOC_SIZEBITS) - 1) - _IOC_DIRMASK = ((1 << _IOC_DIRBITS) - 1) - _IOC_TYPESHIFT = (_IOC_NRBITS) - _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS) - _IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS) - _DM_IOCTL = 0xfd _DM_IOCTL_SIZE = 312 - _DM_IOCTL_BASE = (_IOC_READ|_IOC_WRITE)<<_IOC_DIRSHIFT | _DM_IOCTL<<_IOC_TYPESHIFT | _DM_IOCTL_SIZE<<_IOC_SIZESHIFT + _DM_IOCTL_BASE = linux.IocWRBase | _DM_IOCTL< len(msgReportIn.ReportData) { + return nil, fmt.Errorf("reportData too large: %s", reportData) + } + copy(msgReportIn.ReportData[:], reportData) + } + + payload := &guestRequest{ + RequestMsgType: msgReportRequest, + ResponseMsgType: msgReportResponse, + MsgVersion: 1, + RequestLength: uint16(unsafe.Sizeof(msgReportIn)), + RequestUAddr: unsafe.Pointer(&msgReportIn), + ResponseLength: uint16(unsafe.Sizeof(msgReportOut)), + ResponseUAddr: unsafe.Pointer(&msgReportOut), + Error: 0, + } + + if err := linux.Ioctl(f, reportCode|ioctlBase, unsafe.Pointer(payload)); err != nil { + return nil, err + } + return msgReportOut.Report[:], nil +} + +// Report represents parsed attestation report. +type Report struct { + Version uint32 + GuestSVN uint32 + Policy uint64 + FamilyID string + ImageID string + VMPL uint32 + SignatureAlgo uint32 + PlatformVersion uint64 + PlatformInfo uint64 + AuthorKeyEn uint32 + ReportData string + Measurement string + HostData []byte + IDKeyDigest string + AuthorKeyDigest string + ReportID string + ReportIDMA string + ReportTCB uint64 + ChipID string + CommittedSVN string + CommittedVersion string + LaunchSVN string + Signature string +} + +// mirrorBytes mirrors the byte ordering so that hex-encoding little endian +// ordered bytes come out in the readable order. +func mirrorBytes(b []byte) []byte { + for i := 0; i < len(b)/2; i++ { + mirrorIndex := len(b) - i - 1 + b[i], b[mirrorIndex] = b[mirrorIndex], b[i] + } + return b +} + +// FetchParsedSNPReport parses raw attestation response into proper structs. +func FetchParsedSNPReport(reportData []byte) (Report, error) { + rawBytes, err := FetchRawSNPReport(reportData) + if err != nil { + return Report{}, err + } + + var r report + buf := bytes.NewBuffer(rawBytes) + if err := binary.Read(buf, binary.LittleEndian, &r); err != nil { + return Report{}, err + } + return r.report(), nil +} diff --git a/pkg/amdsevsnp/report_test.go b/pkg/amdsevsnp/report_test.go new file mode 100644 index 0000000000..e42af6de1f --- /dev/null +++ b/pkg/amdsevsnp/report_test.go @@ -0,0 +1,53 @@ +//go:build linux +// +build linux + +package amdsevsnp + +import ( + "testing" +) + +func Test_Mirror_NonEmpty_Byte_Slices(t *testing.T) { + type config struct { + name string + input []byte + expected []byte + } + + for _, conf := range []config{ + { + name: "Length0", + input: []byte{}, + expected: []byte{}, + }, + { + name: "Length1", + input: []byte{100}, + expected: []byte{100}, + }, + { + name: "LengthOdd", + input: []byte{100, 101, 102, 103, 104}, + expected: []byte{104, 103, 102, 101, 100}, + }, + { + name: "LengthEven", + input: []byte{100, 101, 102, 103, 104, 105}, + expected: []byte{105, 104, 103, 102, 101, 100}, + }, + } { + t.Run(conf.name, func(t *testing.T) { + result := mirrorBytes(conf.input) + if string(result[:]) != string(conf.expected[:]) { + t.Fatalf("the ipnut byte array %+v was not mirrored; %+v", conf.input, result) + } + }) + } +} + +func Test_Mirror_Nil_Slice(t *testing.T) { + result := mirrorBytes(nil) + if result != nil { + t.Fatalf("expected nil slice, got: %+v", result) + } +} diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index 2a539ed9db..a75ada7030 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -1,8 +1,10 @@ package securitypolicy import ( + "crypto/sha256" "encoding/base64" "encoding/json" + "fmt" "regexp" "strconv" @@ -86,6 +88,19 @@ func NewOpenDoorPolicy() *SecurityPolicy { } } +// NewSecurityPolicyDigest decodes base64 encoded policy string, computes +// and returns sha256 digest +func NewSecurityPolicyDigest(base64policy string) ([]byte, error) { + jsonPolicy, err := base64.StdEncoding.DecodeString(base64policy) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 security policy: %w", err) + } + digest := sha256.New() + digest.Write(jsonPolicy) + digestBytes := digest.Sum(nil) + return digestBytes, nil +} + // Internal version of SecurityPolicyContainer type securityPolicyContainer struct { // The command that we will allow the container to execute diff --git a/test/vendor/github.com/Microsoft/hcsshim/Makefile b/test/vendor/github.com/Microsoft/hcsshim/Makefile index e1a0f51129..c11786ce3a 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/Makefile +++ b/test/vendor/github.com/Microsoft/hcsshim/Makefile @@ -1,4 +1,5 @@ BASE:=base.tar.gz +DEV_BUILD:=0 GO:=go GO_FLAGS:=-ldflags "-s -w" # strip Go binaries @@ -16,6 +17,12 @@ GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA) SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +DELTA_TARGET=out/delta.tar.gz + +ifeq "$(DEV_BUILD)" "1" +DELTA_TARGET=out/delta-dev.tar.gz +endif + # The link aliases for gcstools GCS_TOOLS=\ generichook \ @@ -55,6 +62,15 @@ out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools bin/cmd/ho tar -zcf $@ -C rootfs . rm -rf rootfs +# This target includes utilities which may be useful for testing purposes. +out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report + rm -rf rootfs-dev + mkdir rootfs-dev + tar -xzf out/delta.tar.gz -C rootfs-dev + cp bin/internal/tools/snp-report rootfs-dev/bin/ + tar -zcf $@ -C rootfs-dev . + rm -rf rootfs-dev + out/rootfs.tar.gz: out/initrd.img rm -rf rootfs-conv mkdir rootfs-conv @@ -62,8 +78,8 @@ out/rootfs.tar.gz: out/initrd.img tar -zcf $@ -C rootfs-conv . rm -rf rootfs-conv -out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh - $(SRCROOT)/hack/catcpio.sh "$(BASE)" out/delta.tar.gz > out/initrd.img.uncompressed +out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh + $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed gzip -c out/initrd.img.uncompressed > $@ rm out/initrd.img.uncompressed @@ -71,6 +87,7 @@ out/initrd.img: $(BASE) out/delta.tar.gz $(SRCROOT)/hack/catcpio.sh -include deps/cmd/gcstools.gomake -include deps/cmd/hooks/wait-paths.gomake -include deps/cmd/tar2ext4.gomake +-include deps/internal/tools/snp-report.gomake # Implicit rule for includes that define Go targets. %.gomake: $(SRCROOT)/Makefile diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go index 3acfa875c3..79a065454e 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go @@ -4,7 +4,6 @@ package uvm import ( "context" - "crypto/sha256" "encoding/base64" "fmt" "io" @@ -15,6 +14,7 @@ import ( "github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio/pkg/guid" + "github.com/Microsoft/hcsshim/pkg/securitypolicy" "github.com/pkg/errors" "github.com/sirupsen/logrus" "go.opencensus.io/trace" @@ -402,28 +402,23 @@ func makeLCOWSecurityDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) // and can be used to check that the policy used by opengcs is the required one as // a condition of releasing secrets to the container. - // First, decode the base64 string into a human readable (json) string . - jsonPolicy, err := base64.StdEncoding.DecodeString(opts.SecurityPolicy) + policyDigest, err := securitypolicy.NewSecurityPolicyDigest(opts.SecurityPolicy) if err != nil { - return nil, fmt.Errorf("failed to decode base64 SecurityPolicy") + return nil, err } - - // make a sha256 hashing object - hostData := sha256.New() - // give it the jsaon string to measure - hostData.Write(jsonPolicy) - // get the measurement out - securityPolicyHash := base64.StdEncoding.EncodeToString(hostData.Sum(nil)) + // HCS API expect a base64 encoded string as LaunchData. Internally it + // decodes it to bytes. SEV later returns the decoded byte blob as HostData + // field of the report. + hostData := base64.StdEncoding.EncodeToString(policyDigest) // Put the measurement into the LaunchData field of the HCS creation command. - // This will endup in HOST_DATA of SNP_LAUNCH_FINISH command the and ATTESTATION_REPORT + // This will end-up in HOST_DATA of SNP_LAUNCH_FINISH command the and ATTESTATION_REPORT // retrieved by the guest later. - doc.VirtualMachine.SecuritySettings = &hcsschema.SecuritySettings{ EnableTpm: false, Isolation: &hcsschema.IsolationSettings{ IsolationType: "SecureNestedPaging", - LaunchData: securityPolicyHash, + LaunchData: hostData, // HclEnabled: true, /* Not available in schema 2.5 - REQUIRED when using BlockStorage in 2.6 */ }, } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go index 2a539ed9db..a75ada7030 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -1,8 +1,10 @@ package securitypolicy import ( + "crypto/sha256" "encoding/base64" "encoding/json" + "fmt" "regexp" "strconv" @@ -86,6 +88,19 @@ func NewOpenDoorPolicy() *SecurityPolicy { } } +// NewSecurityPolicyDigest decodes base64 encoded policy string, computes +// and returns sha256 digest +func NewSecurityPolicyDigest(base64policy string) ([]byte, error) { + jsonPolicy, err := base64.StdEncoding.DecodeString(base64policy) + if err != nil { + return nil, fmt.Errorf("failed to decode base64 security policy: %w", err) + } + digest := sha256.New() + digest.Write(jsonPolicy) + digestBytes := digest.Sum(nil) + return digestBytes, nil +} + // Internal version of SecurityPolicyContainer type securityPolicyContainer struct { // The command that we will allow the container to execute From ccec73f6d54f32aa46ad8c3632162106946b6f7e Mon Sep 17 00:00:00 2001 From: Danny Canter <36526702+dcantah@users.noreply.github.com> Date: Thu, 14 Apr 2022 16:21:18 -0400 Subject: [PATCH 03/16] Swap to fmt.Errorf in jobobject package (#1353) * Swap to fmt.Errorf in jobobject package This change swaps to fmt.Errorf for wrapped errors in the jobobject package from errors.Wrap. We'd had talks of moving this code to winio where we've removed our pkg/errors dependency so this would make the port easier. Signed-off-by: Daniel Canter --- internal/jobobject/jobobject.go | 14 +++++++------- internal/jobobject/jobobject_test.go | 2 +- internal/jobobject/limits.go | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/jobobject/jobobject.go b/internal/jobobject/jobobject.go index 338a682d92..7f2003f155 100644 --- a/internal/jobobject/jobobject.go +++ b/internal/jobobject/jobobject.go @@ -4,6 +4,7 @@ package jobobject import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -13,7 +14,6 @@ import ( "github.com/Microsoft/hcsshim/internal/queue" "github.com/Microsoft/hcsshim/internal/winapi" - "github.com/pkg/errors" "golang.org/x/sys/windows" ) @@ -235,7 +235,7 @@ func setupNotifications(ctx context.Context, job *JobObject) (*queue.MessageQueu jobMap.Store(uintptr(job.handle), mq) if err := attachIOCP(job.handle, ioCompletionPort); err != nil { jobMap.Delete(uintptr(job.handle)) - return nil, errors.Wrap(err, "failed to attach job to IO completion port") + return nil, fmt.Errorf("failed to attach job to IO completion port: %w", err) } return mq, nil } @@ -362,7 +362,7 @@ func (job *JobObject) Pids() ([]uint32, error) { } if err != winapi.ERROR_MORE_DATA { - return nil, errors.Wrap(err, "failed initial query for PIDs in job object") + return nil, fmt.Errorf("failed initial query for PIDs in job object: %w", err) } jobBasicProcessIDListSize := unsafe.Sizeof(info) + (unsafe.Sizeof(info.ProcessIdList[0]) * uintptr(info.NumberOfAssignedProcesses-1)) @@ -374,7 +374,7 @@ func (job *JobObject) Pids() ([]uint32, error) { uint32(len(buf)), nil, ); err != nil { - return nil, errors.Wrap(err, "failed to query for PIDs in job object") + return nil, fmt.Errorf("failed to query for PIDs in job object: %w", err) } bufInfo := (*winapi.JOBOBJECT_BASIC_PROCESS_ID_LIST)(unsafe.Pointer(&buf[0])) @@ -402,7 +402,7 @@ func (job *JobObject) QueryMemoryStats() (*winapi.JOBOBJECT_MEMORY_USAGE_INFORMA uint32(unsafe.Sizeof(info)), nil, ); err != nil { - return nil, errors.Wrap(err, "failed to query for job object memory stats") + return nil, fmt.Errorf("failed to query for job object memory stats: %w", err) } return &info, nil } @@ -424,7 +424,7 @@ func (job *JobObject) QueryProcessorStats() (*winapi.JOBOBJECT_BASIC_ACCOUNTING_ uint32(unsafe.Sizeof(info)), nil, ); err != nil { - return nil, errors.Wrap(err, "failed to query for job object process stats") + return nil, fmt.Errorf("failed to query for job object process stats: %w", err) } return &info, nil } @@ -446,7 +446,7 @@ func (job *JobObject) QueryStorageStats() (*winapi.JOBOBJECT_BASIC_AND_IO_ACCOUN uint32(unsafe.Sizeof(info)), nil, ); err != nil { - return nil, errors.Wrap(err, "failed to query for job object storage stats") + return nil, fmt.Errorf("failed to query for job object storage stats: %w", err) } return &info, nil } diff --git a/internal/jobobject/jobobject_test.go b/internal/jobobject/jobobject_test.go index b885dc6ad4..da4394471c 100644 --- a/internal/jobobject/jobobject_test.go +++ b/internal/jobobject/jobobject_test.go @@ -4,6 +4,7 @@ package jobobject import ( "context" + "errors" "os" "os/exec" "path/filepath" @@ -11,7 +12,6 @@ import ( "testing" "time" - "github.com/pkg/errors" "golang.org/x/sys/windows" ) diff --git a/internal/jobobject/limits.go b/internal/jobobject/limits.go index fd69d9e341..8c0c979402 100644 --- a/internal/jobobject/limits.go +++ b/internal/jobobject/limits.go @@ -3,11 +3,11 @@ package jobobject import ( + "errors" "fmt" "unsafe" "github.com/Microsoft/hcsshim/internal/winapi" - "github.com/pkg/errors" "golang.org/x/sys/windows" ) @@ -24,23 +24,23 @@ func (job *JobObject) SetResourceLimits(limits *JobLimits) error { // Go through and check what limits were specified and apply them to the job. if limits.MemoryLimitInBytes != 0 { if err := job.SetMemoryLimit(limits.MemoryLimitInBytes); err != nil { - return errors.Wrap(err, "failed to set job object memory limit") + return fmt.Errorf("failed to set job object memory limit: %w", err) } } if limits.CPULimit != 0 { if err := job.SetCPULimit(RateBased, limits.CPULimit); err != nil { - return errors.Wrap(err, "failed to set job object cpu limit") + return fmt.Errorf("failed to set job object cpu limit: %w", err) } } else if limits.CPUWeight != 0 { if err := job.SetCPULimit(WeightBased, limits.CPUWeight); err != nil { - return errors.Wrap(err, "failed to set job object cpu limit") + return fmt.Errorf("failed to set job object cpu limit: %w", err) } } if limits.MaxBandwidth != 0 || limits.MaxIOPS != 0 { if err := job.SetIOLimit(limits.MaxBandwidth, limits.MaxIOPS); err != nil { - return errors.Wrap(err, "failed to set io limit on job object") + return fmt.Errorf("failed to set io limit on job object: %w", err) } } return nil @@ -208,7 +208,7 @@ func (job *JobObject) getExtendedInformation() (*windows.JOBOBJECT_EXTENDED_LIMI uint32(unsafe.Sizeof(info)), nil, ); err != nil { - return nil, errors.Wrapf(err, "query %v returned error", info) + return nil, fmt.Errorf("query %v returned error: %w", info, err) } return &info, nil } @@ -230,7 +230,7 @@ func (job *JobObject) getCPURateControlInformation() (*winapi.JOBOBJECT_CPU_RATE uint32(unsafe.Sizeof(info)), nil, ); err != nil { - return nil, errors.Wrapf(err, "query %v returned error", info) + return nil, fmt.Errorf("query %v returned error: %w", info, err) } return &info, nil } @@ -250,7 +250,7 @@ func (job *JobObject) setExtendedInformation(info *windows.JOBOBJECT_EXTENDED_LI uintptr(unsafe.Pointer(info)), uint32(unsafe.Sizeof(*info)), ); err != nil { - return errors.Wrapf(err, "failed to set Extended info %v on job object", info) + return fmt.Errorf("failed to set Extended info %v on job object: %w", info, err) } return nil } @@ -273,7 +273,7 @@ func (job *JobObject) getIOLimit() (*winapi.JOBOBJECT_IO_RATE_CONTROL_INFORMATIO &ioInfo, &blockCount, ); err != nil { - return nil, errors.Wrapf(err, "query %v returned error", ioInfo) + return nil, fmt.Errorf("query %v returned error: %w", ioInfo, err) } if !isFlagSet(winapi.JOB_OBJECT_IO_RATE_CONTROL_ENABLE, ioInfo.ControlFlags) { @@ -292,7 +292,7 @@ func (job *JobObject) setIORateControlInfo(ioInfo *winapi.JOBOBJECT_IO_RATE_CONT } if _, err := winapi.SetIoRateControlInformationJobObject(job.handle, ioInfo); err != nil { - return errors.Wrapf(err, "failed to set IO limit info %v on job object", ioInfo) + return fmt.Errorf("failed to set IO limit info %v on job object: %w", ioInfo, err) } return nil } @@ -311,7 +311,7 @@ func (job *JobObject) setCPURateControlInfo(cpuInfo *winapi.JOBOBJECT_CPU_RATE_C uintptr(unsafe.Pointer(cpuInfo)), uint32(unsafe.Sizeof(cpuInfo)), ); err != nil { - return errors.Wrapf(err, "failed to set cpu limit info %v on job object", cpuInfo) + return fmt.Errorf("failed to set cpu limit info %v on job object: %w", cpuInfo, err) } return nil } From 655b7e11fd2297529b21291cd7cce313a5d76b68 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Fri, 15 Apr 2022 04:03:50 -0700 Subject: [PATCH 04/16] Add support for mount policy enforcement. (#1311) * Add support for mount policy enforcement. It is possible that a malicious mount can be used to attack an LCOW pod. If the attacker has knowleldge of the workload running, they could possibly change the environment for a container and alter its execution. This PR adds support for describing and enforcing mount policy for a given container. The mount policy closely follows OCI spec to be as explicit as possible to the user. The policy can be made less explicit in the future if needed. The dev tool has been updated to support mount configurations and the configuration spec is similar to CRI config with the exception of unsupported features (e.g., selinux config). The tool translates CRI config to appropriate mount type and options in mount policy. Initial implementation doesn't support any wildcards for the Destination, but supports REGEX for the Source. CRI adds some default mounts for all Linux containers and they had to be hardcoded in this codebase as well. Extra caution is needed in the future, in case the list expands. Additional changes have been made to how sandbox and hugepages mounts are generated to make sure that the same utility functions are used to generate appropriate mount specs. Add positive and negative tests for security policy mount constraints Hide mount enforcement behind a LCOWIntegrity feature flag Update securitypolicy tool docs Signed-off-by: Maksim An --- internal/guest/policy/default.go | 79 ++ internal/guest/runtime/hcsv2/container.go | 5 +- .../guest/runtime/hcsv2/sandbox_container.go | 22 +- internal/guest/runtime/hcsv2/spec.go | 58 +- .../runtime/hcsv2/standalone_container.go | 7 +- internal/guest/runtime/hcsv2/uvm.go | 25 +- .../guest/runtime/hcsv2/workload_container.go | 56 +- internal/guest/spec/spec.go | 92 +++ .../mountmonitoringsecuritypolicyenforcer.go | 16 +- internal/guestpath/paths.go | 26 +- internal/tools/securitypolicy/README.md | 47 +- .../tools/securitypolicy/helpers/helpers.go | 4 +- pkg/securitypolicy/opts.go | 9 + pkg/securitypolicy/securitypolicy.go | 219 ++++-- pkg/securitypolicy/securitypolicy_test.go | 3 + pkg/securitypolicy/securitypolicyenforcer.go | 304 +++++++- test/cri-containerd/layer_integrity_test.go | 2 +- test/cri-containerd/policy_test.go | 394 +++++++++- test/go.mod | 1 - .../hcsshim/internal/guest/spec/spec.go | 92 +++ .../hcsshim/internal/guestpath/paths.go | 26 +- .../tools/securitypolicy/helpers/helpers.go | 4 +- .../hcsshim/pkg/securitypolicy/opts.go | 9 + .../pkg/securitypolicy/securitypolicy.go | 219 ++++-- .../securitypolicy/securitypolicyenforcer.go | 304 +++++++- test/vendor/github.com/google/go-cmp/LICENSE | 27 - .../github.com/google/go-cmp/cmp/compare.go | 682 ------------------ .../google/go-cmp/cmp/export_panic.go | 15 - .../google/go-cmp/cmp/export_unsafe.go | 35 - .../go-cmp/cmp/internal/diff/debug_disable.go | 17 - .../go-cmp/cmp/internal/diff/debug_enable.go | 122 ---- .../google/go-cmp/cmp/internal/diff/diff.go | 398 ---------- .../google/go-cmp/cmp/internal/flags/flags.go | 9 - .../cmp/internal/flags/toolchain_legacy.go | 10 - .../cmp/internal/flags/toolchain_recent.go | 10 - .../go-cmp/cmp/internal/function/func.go | 99 --- .../google/go-cmp/cmp/internal/value/name.go | 157 ---- .../cmp/internal/value/pointer_purego.go | 33 - .../cmp/internal/value/pointer_unsafe.go | 36 - .../google/go-cmp/cmp/internal/value/sort.go | 106 --- .../google/go-cmp/cmp/internal/value/zero.go | 48 -- .../github.com/google/go-cmp/cmp/options.go | 552 -------------- .../github.com/google/go-cmp/cmp/path.go | 378 ---------- .../github.com/google/go-cmp/cmp/report.go | 54 -- .../google/go-cmp/cmp/report_compare.go | 432 ----------- .../google/go-cmp/cmp/report_references.go | 264 ------- .../google/go-cmp/cmp/report_reflect.go | 402 ----------- .../google/go-cmp/cmp/report_slices.go | 613 ---------------- .../google/go-cmp/cmp/report_text.go | 431 ----------- .../google/go-cmp/cmp/report_value.go | 121 ---- test/vendor/modules.txt | 8 +- 51 files changed, 1677 insertions(+), 5405 deletions(-) create mode 100644 internal/guest/policy/default.go create mode 100644 internal/guest/spec/spec.go create mode 100644 test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go delete mode 100644 test/vendor/github.com/google/go-cmp/LICENSE delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/compare.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/export_panic.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/export_unsafe.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/function/func.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/value/name.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/options.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/path.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report_compare.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report_references.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report_reflect.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report_slices.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report_text.go delete mode 100644 test/vendor/github.com/google/go-cmp/cmp/report_value.go diff --git a/internal/guest/policy/default.go b/internal/guest/policy/default.go new file mode 100644 index 0000000000..b75ad62d7c --- /dev/null +++ b/internal/guest/policy/default.go @@ -0,0 +1,79 @@ +//go:build linux +// +build linux + +package policy + +import ( + oci "github.com/opencontainers/runtime-spec/specs-go" + + internalSpec "github.com/Microsoft/hcsshim/internal/guest/spec" + "github.com/Microsoft/hcsshim/pkg/securitypolicy" +) + +func ExtendPolicyWithNetworkingMounts(sandboxID string, enforcer securitypolicy.SecurityPolicyEnforcer, spec *oci.Spec) error { + roSpec := &oci.Spec{ + Root: spec.Root, + } + networkingMounts := internalSpec.GenerateWorkloadContainerNetworkMounts(sandboxID, roSpec) + if err := enforcer.ExtendDefaultMounts(networkingMounts); err != nil { + return err + } + return nil +} + +// DefaultCRIMounts returns default mounts added to linux spec by containerD. +func DefaultCRIMounts() []oci.Mount { + return []oci.Mount{ + { + Destination: "/proc", + Type: "proc", + Source: "proc", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/dev", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + { + Destination: "/dev/pts", + Type: "devpts", + Source: "devpts", + Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"}, + }, + { + Destination: "/dev/shm", + Type: "tmpfs", + Source: "shm", + Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"}, + }, + { + Destination: "/dev/mqueue", + Type: "mqueue", + Source: "mqueue", + Options: []string{"nosuid", "noexec", "nodev"}, + }, + { + Destination: "/sys", + Type: "sysfs", + Source: "sysfs", + Options: []string{"nosuid", "noexec", "nodev", "ro"}, + }, + { + Destination: "/run", + Type: "tmpfs", + Source: "tmpfs", + Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"}, + }, + // cgroup mount is always added by default, regardless if it is present + // in the mount constraints or not. If the user chooses to override it, + // then a corresponding mount constraint should be present. + { + Source: "cgroup", + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"}, + }, + } +} diff --git a/internal/guest/runtime/hcsv2/container.go b/internal/guest/runtime/hcsv2/container.go index d222842e6b..d890d0c2b0 100644 --- a/internal/guest/runtime/hcsv2/container.go +++ b/internal/guest/runtime/hcsv2/container.go @@ -18,6 +18,7 @@ import ( "github.com/Microsoft/hcsshim/internal/guest/gcserr" "github.com/Microsoft/hcsshim/internal/guest/prot" "github.com/Microsoft/hcsshim/internal/guest/runtime" + specInternal "github.com/Microsoft/hcsshim/internal/guest/spec" "github.com/Microsoft/hcsshim/internal/guest/stdio" "github.com/Microsoft/hcsshim/internal/guest/storage" "github.com/Microsoft/hcsshim/internal/guest/transport" @@ -158,12 +159,12 @@ func (c *Container) Delete(ctx context.Context) error { entity.Info("opengcs::Container::Delete") if c.isSandbox { // remove user mounts in sandbox container - if err := storage.UnmountAllInPath(ctx, getSandboxMountsDir(c.id), true); err != nil { + if err := storage.UnmountAllInPath(ctx, specInternal.SandboxMountsDir(c.id), true); err != nil { entity.WithError(err).Error("failed to unmount sandbox mounts") } // remove hugepages mounts in sandbox container - if err := storage.UnmountAllInPath(ctx, getSandboxHugePageMountsDir(c.id), true); err != nil { + if err := storage.UnmountAllInPath(ctx, specInternal.HugePagesMountsDir(c.id), true); err != nil { entity.WithError(err).Error("failed to unmount hugepages mounts") } } diff --git a/internal/guest/runtime/hcsv2/sandbox_container.go b/internal/guest/runtime/hcsv2/sandbox_container.go index c76b11d3ce..45ad286016 100644 --- a/internal/guest/runtime/hcsv2/sandbox_container.go +++ b/internal/guest/runtime/hcsv2/sandbox_container.go @@ -15,33 +15,21 @@ import ( "go.opencensus.io/trace" "github.com/Microsoft/hcsshim/internal/guest/network" - "github.com/Microsoft/hcsshim/internal/guestpath" + specInternal "github.com/Microsoft/hcsshim/internal/guest/spec" "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/pkg/annotations" ) -func getSandboxRootDir(id string) string { - return filepath.Join(guestpath.LCOWRootPrefixInUVM, id) -} - -func getSandboxHugePageMountsDir(id string) string { - return filepath.Join(getSandboxRootDir(id), "hugepages") -} - -func getSandboxMountsDir(id string) string { - return filepath.Join(getSandboxRootDir(id), "sandboxMounts") -} - func getSandboxHostnamePath(id string) string { - return filepath.Join(getSandboxRootDir(id), "hostname") + return filepath.Join(specInternal.SandboxRootDir(id), "hostname") } func getSandboxHostsPath(id string) string { - return filepath.Join(getSandboxRootDir(id), "hosts") + return filepath.Join(specInternal.SandboxRootDir(id), "hosts") } func getSandboxResolvPath(id string) string { - return filepath.Join(getSandboxRootDir(id), "resolv.conf") + return filepath.Join(specInternal.SandboxRootDir(id), "resolv.conf") } func setupSandboxContainerSpec(ctx context.Context, id string, spec *oci.Spec) (err error) { @@ -51,7 +39,7 @@ func setupSandboxContainerSpec(ctx context.Context, id string, spec *oci.Spec) ( span.AddAttributes(trace.StringAttribute("cid", id)) // Generate the sandbox root dir - rootDir := getSandboxRootDir(id) + rootDir := specInternal.SandboxRootDir(id) if err := os.MkdirAll(rootDir, 0755); err != nil { return errors.Wrapf(err, "failed to create sandbox root directory %q", rootDir) } diff --git a/internal/guest/runtime/hcsv2/spec.go b/internal/guest/runtime/hcsv2/spec.go index 5241ba6431..0dbc491830 100644 --- a/internal/guest/runtime/hcsv2/spec.go +++ b/internal/guest/runtime/hcsv2/spec.go @@ -20,6 +20,10 @@ import ( "github.com/Microsoft/hcsshim/pkg/annotations" ) +const ( + devShmPath = "/dev/shm" +) + // getNetworkNamespaceID returns the `ToLower` of // `spec.Windows.Network.NetworkNamespace` or `""`. func getNetworkNamespaceID(spec *oci.Spec) string { @@ -38,17 +42,6 @@ func isRootReadonly(spec *oci.Spec) bool { return false } -// isInMounts returns `true` if `target` matches a `Destination` in any of -// `mounts`. -func isInMounts(target string, mounts []oci.Mount) bool { - for _, m := range mounts { - if m.Destination == target { - return true - } - } - return false -} - // removeMount removes mount from the array if `target` matches `Destination` func removeMount(target string, mounts []oci.Mount) []oci.Mount { var result []oci.Mount @@ -202,25 +195,13 @@ func getGroup(spec *oci.Spec, filter func(user.Group) bool) (user.Group, error) func applyAnnotationsToSpec(ctx context.Context, spec *oci.Spec) error { // Check if we need to override container's /dev/shm if val, ok := spec.Annotations[annotations.LCOWDevShmSizeInKb]; ok { - sz, err := strconv.ParseInt(val, 10, 64) + mt, err := devShmMountWithSize(val) if err != nil { - return errors.Wrap(err, "/dev/shm size must be a valid integer") - } - if sz <= 0 { - return errors.Errorf("/dev/shm size must be a positive integer, got: %d", sz) - } - - // Use the same options as in upstream https://github.com/containerd/containerd/blob/0def98e462706286e6eaeff4a90be22fda75e761/oci/mounts.go#L49 - size := fmt.Sprintf("size=%dk", sz) - mt := oci.Mount{ - Destination: "/dev/shm", - Type: "tmpfs", - Source: "shm", - Options: []string{"nosuid", "noexec", "nodev", "mode=1777", size}, + return err } - spec.Mounts = removeMount("/dev/shm", spec.Mounts) - spec.Mounts = append(spec.Mounts, mt) - log.G(ctx).WithField("size", size).Debug("set custom /dev/shm size") + spec.Mounts = removeMount(devShmPath, spec.Mounts) + spec.Mounts = append(spec.Mounts, *mt) + log.G(ctx).WithField("sizeKB", val).Debug("set custom /dev/shm size") } // Check if we need to do any capability/device mappings @@ -263,3 +244,24 @@ func addLDConfigHook(_ context.Context, spec *oci.Spec, args, env []string) erro ldConfigHook := hooks.NewOCIHook("/sbin/ldconfig", args, env) return hooks.AddOCIHook(spec, hooks.Prestart, ldConfigHook) } + +// devShmMountWithSize returns a /dev/shm device mount with size set to +// `sizeString` if it represents a valid size in KB, returns error otherwise. +func devShmMountWithSize(sizeString string) (*oci.Mount, error) { + size, err := strconv.ParseUint(sizeString, 10, 64) + if err != nil { + return nil, fmt.Errorf("/dev/shm size must be a valid integer: %w", err) + } + if size == 0 { + return nil, errors.New("/dev/shm size must be non-zero") + } + + // Use the same options as in upstream https://github.com/containerd/containerd/blob/0def98e462706286e6eaeff4a90be22fda75e761/oci/mounts.go#L49 + sizeKB := fmt.Sprintf("size=%sk", sizeString) + return &oci.Mount{ + Source: "shm", + Destination: devShmPath, + Type: "tmpfs", + Options: []string{"nosuid", "noexec", "nodev", "mode=1777", sizeKB}, + }, nil +} diff --git a/internal/guest/runtime/hcsv2/standalone_container.go b/internal/guest/runtime/hcsv2/standalone_container.go index 89189313e8..0e232e7eda 100644 --- a/internal/guest/runtime/hcsv2/standalone_container.go +++ b/internal/guest/runtime/hcsv2/standalone_container.go @@ -15,6 +15,7 @@ import ( "go.opencensus.io/trace" "github.com/Microsoft/hcsshim/internal/guest/network" + specInternal "github.com/Microsoft/hcsshim/internal/guest/spec" "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/Microsoft/hcsshim/internal/oc" ) @@ -62,7 +63,7 @@ func setupStandaloneContainerSpec(ctx context.Context, id string, spec *oci.Spec } // Write the hostname - if !isInMounts("/etc/hostname", spec.Mounts) { + if !specInternal.MountPresent("/etc/hostname", spec.Mounts) { standaloneHostnamePath := getStandaloneHostnamePath(id) if err := ioutil.WriteFile(standaloneHostnamePath, []byte(hostname+"\n"), 0644); err != nil { return errors.Wrapf(err, "failed to write hostname to %q", standaloneHostnamePath) @@ -81,7 +82,7 @@ func setupStandaloneContainerSpec(ctx context.Context, id string, spec *oci.Spec } // Write the hosts - if !isInMounts("/etc/hosts", spec.Mounts) { + if !specInternal.MountPresent("/etc/hosts", spec.Mounts) { standaloneHostsContent := network.GenerateEtcHostsContent(ctx, hostname) standaloneHostsPath := getStandaloneHostsPath(id) if err := ioutil.WriteFile(standaloneHostsPath, []byte(standaloneHostsContent), 0644); err != nil { @@ -101,7 +102,7 @@ func setupStandaloneContainerSpec(ctx context.Context, id string, spec *oci.Spec } // Write resolv.conf - if !isInMounts("/etc/resolv.conf", spec.Mounts) { + if !specInternal.MountPresent("/etc/resolv.conf", spec.Mounts) { ns := getOrAddNetworkNamespace(getNetworkNamespaceID(spec)) var searches, servers []string for _, n := range ns.Adapters() { diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index b1abecc811..568f0ed448 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -16,12 +16,14 @@ import ( "syscall" "time" + "github.com/Microsoft/hcsshim/internal/guest/policy" "github.com/mattn/go-shellwords" "github.com/pkg/errors" "github.com/Microsoft/hcsshim/internal/guest/gcserr" "github.com/Microsoft/hcsshim/internal/guest/prot" "github.com/Microsoft/hcsshim/internal/guest/runtime" + "github.com/Microsoft/hcsshim/internal/guest/spec" "github.com/Microsoft/hcsshim/internal/guest/stdio" "github.com/Microsoft/hcsshim/internal/guest/storage" "github.com/Microsoft/hcsshim/internal/guest/storage/overlay" @@ -104,6 +106,10 @@ func (h *Host) SetSecurityPolicy(base64Policy string) error { return err } + if err := p.ExtendDefaultMounts(policy.DefaultCRIMounts()); err != nil { + return err + } + h.securityPolicyEnforcer = p h.securityPolicyEnforcerSet = true @@ -133,7 +139,7 @@ func (h *Host) GetContainer(id string) (*Container, error) { } func setupSandboxMountsPath(id string) (err error) { - mountPath := getSandboxMountsDir(id) + mountPath := spec.SandboxMountsDir(id) if err := os.MkdirAll(mountPath, 0755); err != nil { return errors.Wrapf(err, "failed to create sandboxMounts dir in sandbox %v", id) } @@ -147,7 +153,7 @@ func setupSandboxMountsPath(id string) (err error) { } func setupSandboxHugePageMountsPath(id string) error { - mountPath := getSandboxHugePageMountsDir(id) + mountPath := spec.HugePagesMountsDir(id) if err := os.MkdirAll(mountPath, 0755); err != nil { return errors.Wrapf(err, "failed to create hugepage Mounts dir in sandbox %v", id) } @@ -176,6 +182,8 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM var namespaceID string criType, isCRI := settings.OCISpecification.Annotations[annotations.KubernetesContainerType] + // for sandbox container sandboxID is same as container id + sandboxID := id if isCRI { switch criType { case "sandbox": @@ -187,7 +195,7 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM } defer func() { if err != nil { - _ = os.RemoveAll(getSandboxRootDir(id)) + _ = os.RemoveAll(spec.SandboxRootDir(id)) } }() @@ -198,8 +206,13 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM if err = setupSandboxHugePageMountsPath(id); err != nil { return nil, err } + + if err := policy.ExtendPolicyWithNetworkingMounts(id, h.securityPolicyEnforcer, settings.OCISpecification); err != nil { + return nil, err + } case "container": sid, ok := settings.OCISpecification.Annotations[annotations.KubernetesSandboxID] + sandboxID = sid if !ok || sid == "" { return nil, errors.Errorf("unsupported 'io.kubernetes.cri.sandbox-id': '%s'", sid) } @@ -211,6 +224,9 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM _ = os.RemoveAll(getWorkloadRootDir(id)) } }() + if err := policy.ExtendPolicyWithNetworkingMounts(sandboxID, h.securityPolicyEnforcer, settings.OCISpecification); err != nil { + return nil, err + } default: return nil, errors.Errorf("unsupported 'io.kubernetes.cri.container-type': '%s'", criType) } @@ -227,6 +243,9 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM }() } + if err := h.securityPolicyEnforcer.EnforceMountPolicy(sandboxID, id, settings.OCISpecification); err != nil { + return nil, err + } // Export security policy as one of the process's environment variables so that application and sidecar // containers can have access to it. The security policy is required by containers which need to extract // init-time claims found in the security policy. diff --git a/internal/guest/runtime/hcsv2/workload_container.go b/internal/guest/runtime/hcsv2/workload_container.go index b9679d9036..45249305a0 100644 --- a/internal/guest/runtime/hcsv2/workload_container.go +++ b/internal/guest/runtime/hcsv2/workload_container.go @@ -14,6 +14,7 @@ import ( "go.opencensus.io/trace" "golang.org/x/sys/unix" + specInternal "github.com/Microsoft/hcsshim/internal/guest/spec" "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/pkg/annotations" @@ -36,13 +37,11 @@ func mkdirAllModePerm(target string) error { func updateSandboxMounts(sbid string, spec *oci.Spec) error { for i, m := range spec.Mounts { if strings.HasPrefix(m.Source, guestpath.SandboxMountPrefix) { - mountsDir := getSandboxMountsDir(sbid) - subPath := strings.TrimPrefix(m.Source, guestpath.SandboxMountPrefix) - sandboxSource := filepath.Join(mountsDir, subPath) + sandboxSource := specInternal.SandboxMountSource(sbid, m.Source) - // filepath.Join cleans the resulting path before returning so it would resolve the relative path if one was given. + // filepath.Join cleans the resulting path before returning, so it would resolve the relative path if one was given. // Hence, we need to ensure that the resolved path is still under the correct directory - if !strings.HasPrefix(sandboxSource, mountsDir) { + if !strings.HasPrefix(sandboxSource, specInternal.SandboxMountsDir(sbid)) { return errors.Errorf("mount path %v for mount %v is not within sandbox's mounts dir", sandboxSource, m.Source) } @@ -62,7 +61,7 @@ func updateSandboxMounts(sbid string, spec *oci.Spec) error { func updateHugePageMounts(sbid string, spec *oci.Spec) error { for i, m := range spec.Mounts { if strings.HasPrefix(m.Source, guestpath.HugePagesMountPrefix) { - mountsDir := getSandboxHugePageMountsDir(sbid) + mountsDir := specInternal.HugePagesMountsDir(sbid) subPath := strings.TrimPrefix(m.Source, guestpath.HugePagesMountPrefix) pageSize := strings.Split(subPath, string(os.PathSeparator))[0] hugePageMountSource := filepath.Join(mountsDir, subPath) @@ -120,47 +119,10 @@ func setupWorkloadContainerSpec(ctx context.Context, sbid, id string, spec *oci. return errors.Wrapf(err, "failed to update hugepages mounts for container %v in sandbox %v", id, sbid) } - // Add /etc/hostname if the spec did not override it. - if !isInMounts("/etc/hostname", spec.Mounts) { - mt := oci.Mount{ - Destination: "/etc/hostname", - Type: "bind", - Source: getSandboxHostnamePath(sbid), - Options: []string{"bind"}, - } - if isRootReadonly(spec) { - mt.Options = append(mt.Options, "ro") - } - spec.Mounts = append(spec.Mounts, mt) - } - - // Add /etc/hosts if the spec did not override it. - if !isInMounts("/etc/hosts", spec.Mounts) { - mt := oci.Mount{ - Destination: "/etc/hosts", - Type: "bind", - Source: getSandboxHostsPath(sbid), - Options: []string{"bind"}, - } - if isRootReadonly(spec) { - mt.Options = append(mt.Options, "ro") - } - spec.Mounts = append(spec.Mounts, mt) - } - - // Add /etc/resolv.conf if the spec did not override it. - if !isInMounts("/etc/resolv.conf", spec.Mounts) { - mt := oci.Mount{ - Destination: "/etc/resolv.conf", - Type: "bind", - Source: getSandboxResolvPath(sbid), - Options: []string{"bind"}, - } - if isRootReadonly(spec) { - mt.Options = append(mt.Options, "ro") - } - spec.Mounts = append(spec.Mounts, mt) - } + // Add default mounts for container networking (e.g. /etc/hostname, /etc/hosts), + // if spec didn't override them explicitly. + networkingMounts := specInternal.GenerateWorkloadContainerNetworkMounts(sbid, spec) + spec.Mounts = append(spec.Mounts, networkingMounts...) // TODO: JTERRY75 /dev/shm is not properly setup for LCOW I believe. CRI // also has a concept of a sandbox/shm file when the IPC NamespaceMode != diff --git a/internal/guest/spec/spec.go b/internal/guest/spec/spec.go new file mode 100644 index 0000000000..842e1822c4 --- /dev/null +++ b/internal/guest/spec/spec.go @@ -0,0 +1,92 @@ +//go:build linux +// +build linux + +// Package spec encapsulates a number of GCS specific oci spec modifications, e.g., +// networking mounts, sandbox path substitutions in guest etc. +// +// TODO: consider moving oci spec specific code from /internal/guest/runtime/hcsv2/spec.go +package spec + +import ( + "path/filepath" + "strings" + + "github.com/Microsoft/hcsshim/internal/guestpath" + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// networkingMountPaths returns an array of mount paths to enable networking +// inside containers. +func networkingMountPaths() []string { + return []string{ + "/etc/hostname", + "/etc/hosts", + "/etc/resolv.conf", + } +} + +// GenerateWorkloadContainerNetworkMounts generates an array of specs.Mount +// required for container networking. Original spec is left untouched and +// it's the responsibility of a caller to update it. +func GenerateWorkloadContainerNetworkMounts(sandboxID string, spec *oci.Spec) []oci.Mount { + var nMounts []oci.Mount + + for _, mountPath := range networkingMountPaths() { + // Don't override if the mount is present in the spec + if MountPresent(mountPath, spec.Mounts) { + continue + } + options := []string{"bind"} + if spec.Root != nil && spec.Root.Readonly { + options = append(options, "ro") + } + trimmedMountPath := strings.TrimPrefix(mountPath, "/etc/") + mt := oci.Mount{ + Destination: mountPath, + Type: "bind", + Source: filepath.Join(SandboxRootDir(sandboxID), trimmedMountPath), + Options: options, + } + nMounts = append(nMounts, mt) + } + return nMounts +} + +// MountPresent checks if mountPath is present in the specMounts array. +func MountPresent(mountPath string, specMounts []oci.Mount) bool { + for _, m := range specMounts { + if m.Destination == mountPath { + return true + } + } + return false +} + +// SandboxRootDir returns the sandbox container root directory inside UVM/host. +func SandboxRootDir(sandboxID string) string { + return filepath.Join(guestpath.LCOWRootPrefixInUVM, sandboxID) +} + +// SandboxMountsDir returns sandbox mounts directory inside UVM/host. +func SandboxMountsDir(sandboxID string) string { + return filepath.Join(SandboxRootDir(sandboxID), "sandboxMounts") +} + +// HugePagesMountsDir returns hugepages mounts directory inside UVM. +func HugePagesMountsDir(sandboxID string) string { + return filepath.Join(SandboxRootDir(sandboxID), "hugepages") +} + +// SandboxMountSource returns sandbox mount path inside UVM +func SandboxMountSource(sandboxID, path string) string { + mountsDir := SandboxMountsDir(sandboxID) + subPath := strings.TrimPrefix(path, guestpath.SandboxMountPrefix) + return filepath.Join(mountsDir, subPath) +} + +// HugePagesMountSource returns hugepages mount path inside UVM +func HugePagesMountSource(sandboxID, path string) string { + mountsDir := HugePagesMountsDir(sandboxID) + subPath := strings.TrimPrefix(path, guestpath.HugePagesMountPrefix) + return filepath.Join(mountsDir, subPath) +} diff --git a/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go b/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go index aa34077fcb..5b088d5311 100644 --- a/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go +++ b/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package policy import ( @@ -6,8 +9,9 @@ import ( "github.com/Microsoft/hcsshim/pkg/securitypolicy" ) -// For testing. Records the number of calls to each method so we can verify -// the expected interactions took place. +// MountMonitoringSecurityPolicyEnforcer is used for testing and records the +// number of calls to each method, so we can verify the expected interactions +// took place. type MountMonitoringSecurityPolicyEnforcer struct { DeviceMountCalls int DeviceUnmountCalls int @@ -35,6 +39,14 @@ func (p *MountMonitoringSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ s return nil } +func (MountMonitoringSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spec) error { + return nil +} + func (p *MountMonitoringSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return nil } + +func (MountMonitoringSecurityPolicyEnforcer) ExtendDefaultMounts(_ []oci.Mount) error { + return nil +} diff --git a/internal/guestpath/paths.go b/internal/guestpath/paths.go index 62bbfeb636..be812ba075 100644 --- a/internal/guestpath/paths.go +++ b/internal/guestpath/paths.go @@ -1,23 +1,29 @@ package guestpath const ( - // LCOWNvidiaMountPath is the path format in LCOW UVM where nvidia tools are mounted - // keep this value in sync with opengcs + // LCOWNvidiaMountPath is the path format in LCOW UVM where nvidia tools + // are mounted keep this value in sync with opengcs LCOWNvidiaMountPath = "/run/nvidia" - // LCOWRootPrefixInUVM is the path inside UVM where LCOW container's root file system will be mounted + // LCOWRootPrefixInUVM is the path inside UVM where LCOW container's root + // file system will be mounted LCOWRootPrefixInUVM = "/run/gcs/c" - // WCOWRootPrefixInUVM is the path inside UVM where WCOW container's root file system will be mounted + // WCOWRootPrefixInUVM is the path inside UVM where WCOW container's root + // file system will be mounted WCOWRootPrefixInUVM = `C:\c` - // SandboxMountPrefix is mount prefix used in container spec to mark a sandbox-mount + // SandboxMountPrefix is mount prefix used in container spec to mark a + // sandbox-mount SandboxMountPrefix = "sandbox://" - // HugePagesMountPrefix is mount prefix used in container spec to mark a huge-pages mount + // HugePagesMountPrefix is mount prefix used in container spec to mark a + // huge-pages mount HugePagesMountPrefix = "hugepages://" - // LCOWMountPathPrefixFmt is the path format in the LCOW UVM where non global mounts, such - // as Plan9 mounts are added + // LCOWMountPathPrefixFmt is the path format in the LCOW UVM where + // non-global mounts, such as Plan9 mounts are added LCOWMountPathPrefixFmt = "/mounts/m%d" - // LCOWGlobalMountPrefixFmt is the path format in the LCOW UVM where global mounts are added + // LCOWGlobalMountPrefixFmt is the path format in the LCOW UVM where global + // mounts are added LCOWGlobalMountPrefixFmt = "/run/mounts/m%d" - // WCOWGlobalMountPrefixFmt is the path prefix format in the WCOW UVM where mounts are added + // WCOWGlobalMountPrefixFmt is the path prefix format in the WCOW UVM where + // mounts are added WCOWGlobalMountPrefixFmt = "C:\\mounts\\m%d" // RootfsPath is part of the container's rootfs path RootfsPath = "rootfs" diff --git a/internal/tools/securitypolicy/README.md b/internal/tools/securitypolicy/README.md index 4efb7c1632..8251dc63ae 100644 --- a/internal/tools/securitypolicy/README.md +++ b/internal/tools/securitypolicy/README.md @@ -26,6 +26,16 @@ expected_mounts = ["/path/to/container/mount-1", "/path/to/container/mount-2"] [[container.env_rule]] strategy = "re2" rule = "PREFIX_.+=.+" + +[[container.mount]] +host_path = "sandbox://host/path/one" +container_path = "/container/path/one" +readonly = false + +[[container.mount]] +host_path = "sandbox://host/path/two" +container_path = "/container/path/two" +readonly = true ``` ### Converted to JSON @@ -94,6 +104,37 @@ represented in JSON. "0": "/path/to/container/mount-1", "1": "/path/to/container/mount-2" } + }, + "mounts": { + "length": 2, + "elements": { + "0": { + "source": "sandbox://host/path/one", + "destination": "/container/path/one", + "type": "bind", + "options": { + "length": 3, + "elements": { + "0": "rbind", + "1": "rprivate", + "2": "rw" + } + } + }, + "1": { + "source": "sandbox://host/path/two", + "destination": "/container/path/two", + "type": "bind", + "options": { + "length": 3, + "elements": { + "0": "rbind", + "1": "rprivate", + "2": "ro" + } + } + } + } } }, "1": { @@ -126,6 +167,10 @@ represented in JSON. "expected_mounts": { "length": 0, "elements": {} + }, + "mounts": { + "length": 0, + "elements": {} } } } @@ -151,7 +196,7 @@ to the TOML definition for that image. For example: ```toml [[container]] -name = "rust:1.52.1" +image_name = "rust:1.52.1" command = ["rustc", "--help"] [auth] diff --git a/internal/tools/securitypolicy/helpers/helpers.go b/internal/tools/securitypolicy/helpers/helpers.go index 103b2bae3a..9170bec6ae 100644 --- a/internal/tools/securitypolicy/helpers/helpers.go +++ b/internal/tools/securitypolicy/helpers/helpers.go @@ -75,6 +75,7 @@ func DefaultContainerConfigs() []securitypolicy.ContainerConfig { securitypolicy.AuthConfig{}, "", []string{}, + []securitypolicy.MountConfig{}, ) return []securitypolicy.ContainerConfig{pause} } @@ -137,12 +138,13 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf workingDir = containerConfig.WorkingDir } - container, err := securitypolicy.NewContainer( + container, err := securitypolicy.CreateContainerPolicy( containerConfig.Command, layerHashes, envRules, workingDir, containerConfig.ExpectedMounts, + containerConfig.Mounts, ) if err != nil { return nil, err diff --git a/pkg/securitypolicy/opts.go b/pkg/securitypolicy/opts.go index 32885ce666..5d64296fba 100644 --- a/pkg/securitypolicy/opts.go +++ b/pkg/securitypolicy/opts.go @@ -25,3 +25,12 @@ func WithWorkingDir(wd string) ContainerConfigOpt { return nil } } + +// WithMountConstraints extends ContainerConfig.Mounts with provided mount +// constraints. +func WithMountConstraints(mc []MountConfig) ContainerConfigOpt { + return func(c *ContainerConfig) error { + c.Mounts = append(c.Mounts, mc...) + return nil + } +} diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index a75ada7030..c9cdf3226f 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -7,7 +7,9 @@ import ( "fmt" "regexp" "strconv" + "strings" + "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/pkg/errors" ) @@ -46,6 +48,15 @@ type ContainerConfig struct { EnvRules []EnvRuleConfig `json:"env_rules" toml:"env_rule"` WorkingDir string `json:"working_dir" toml:"working_dir"` ExpectedMounts []string `json:"expected_mounts" toml:"expected_mounts"` + Mounts []MountConfig `json:"mounts" toml:"mount"` +} + +// MountConfig contains toml or JSON config for mount security policy +// constraint description. +type MountConfig struct { + HostPath string `json:"host_path" toml:"host_path"` + ContainerPath string `json:"container_path" toml:"container_path"` + Readonly bool `json:"readonly" toml:"readonly"` } // NewContainerConfig creates a new ContainerConfig from the given values. @@ -56,6 +67,7 @@ func NewContainerConfig( auth AuthConfig, workingDir string, expectedMounts []string, + mounts []MountConfig, ) ContainerConfig { return ContainerConfig{ ImageName: imageName, @@ -64,6 +76,7 @@ func NewContainerConfig( Auth: auth, WorkingDir: workingDir, ExpectedMounts: expectedMounts, + Mounts: mounts, } } @@ -101,25 +114,6 @@ func NewSecurityPolicyDigest(base64policy string) ([]byte, error) { return digestBytes, nil } -// Internal version of SecurityPolicyContainer -type securityPolicyContainer struct { - // The command that we will allow the container to execute - Command []string - // The rules for determining if a given environment variable is allowed - EnvRules []EnvRuleConfig - // An ordered list of dm-verity root hashes for each layer that makes up - // "a container". Containers are constructed as an overlay file system. The - // order that the layers are overlayed is important and needs to be enforced - // as part of policy. - Layers []string - // WorkingDir is a path to container's working directory, which all the processes - // will default to. - WorkingDir string - // Unordered list of mounts which are expected to be present when the container - // starts - ExpectedMounts []string `json:"expected_mounts"` -} - // SecurityPolicyState is a structure that holds user supplied policy to enforce // we keep both the encoded representation and the unmarshalled representation // because different components need to have access to either of these @@ -135,12 +129,12 @@ type EncodedSecurityPolicy struct { SecurityPolicy string `json:"SecurityPolicy,omitempty"` } -// Constructs SecurityPolicyState from base64Policy string. It first decodes -// base64 policy and returns the structs security policy struct and encoded -// security policy for given policy. The security policy is transmitted as json -// in an annotation, so we first have to remove the base64 encoding that allows -// the JSON based policy to be passed as a string. From there, we decode the -// JSON and setup our security policy struct +// NewSecurityPolicyState constructs SecurityPolicyState from base64Policy +// string. It first decodes base64 policy and returns the security policy +// struct and encoded security policy for given policy. The security policy +// is transmitted as json in an annotation, so we first have to remove the +// base64 encoding that allows the JSON based policy to be passed as a string. +// From there, we decode the JSON and set up our security policy struct func NewSecurityPolicyState(base64Policy string) (*SecurityPolicyState, error) { // construct an encoded security policy that holds the base64 representation encodedSecurityPolicy := EncodedSecurityPolicy{ @@ -170,12 +164,12 @@ func NewSecurityPolicyState(base64Policy string) (*SecurityPolicyState, error) { } type SecurityPolicy struct { - // Flag that when set to true allows for all checks to pass. Currently used + // Flag that when set to true allows for all checks to pass. Currently, used // to run with security policy enforcement "running dark"; checks can be in // place but the default policy that is created on startup has AllowAll set // to true, thus making policy enforcement effectively "off" from a logical // standpoint. Policy enforcement isn't actually off as the policy is "allow - // everything:. + // everything". AllowAll bool `json:"allow_all"` // One or more containers that are allowed to run Containers Containers `json:"containers"` @@ -201,42 +195,62 @@ type Container struct { Layers Layers `json:"layers"` WorkingDir string `json:"working_dir"` ExpectedMounts ExpectedMounts `json:"expected_mounts"` + Mounts Mounts `json:"mounts"` } -type Layers struct { - Length int `json:"length"` - // an ordered list of args where the key is in the index for ordering +// StringArrayMap wraps an array of strings as a string map. +type StringArrayMap struct { + Length int `json:"length"` Elements map[string]string `json:"elements"` } -type CommandArgs struct { - Length int `json:"length"` - // an ordered list of args where the key is in the index for ordering - Elements map[string]string `json:"elements"` -} +type Layers StringArrayMap + +type CommandArgs StringArrayMap + +type ExpectedMounts StringArrayMap + +type Options StringArrayMap type EnvRules struct { Length int `json:"length"` Elements map[string]EnvRuleConfig `json:"elements"` } -type ExpectedMounts struct { - Length int `json:"length"` - Elements map[string]string `json:"elements"` +type Mount struct { + Source string `json:"source"` + Destination string `json:"destination"` + Type string `json:"type"` + Options Options `json:"options"` +} + +type Mounts struct { + Length int `json:"length"` + Elements map[string]Mount `json:"elements"` } -// NewContainer creates a new Container instance from the provided values -// or an error if envRules validation fails. -func NewContainer(command, layers []string, envRules []EnvRuleConfig, workingDir string, eMounts []string) (*Container, error) { +// CreateContainerPolicy creates a new Container policy instance from the +// provided constraints or an error if parameter validation fails. +func CreateContainerPolicy( + command, layers []string, + envRules []EnvRuleConfig, + workingDir string, + eMounts []string, + mounts []MountConfig, +) (*Container, error) { if err := validateEnvRules(envRules); err != nil { return nil, err } + if err := validateMountConstraint(mounts); err != nil { + return nil, err + } return &Container{ Command: newCommandArgs(command), Layers: newLayers(layers), EnvRules: newEnvRules(envRules), WorkingDir: workingDir, ExpectedMounts: newExpectedMounts(eMounts), + Mounts: newMountConstraints(mounts), }, nil } @@ -266,6 +280,15 @@ func validateEnvRules(rules []EnvRuleConfig) error { return nil } +func validateMountConstraint(mounts []MountConfig) error { + for _, m := range mounts { + if _, err := regexp.Compile(m.HostPath); err != nil { + return err + } + } + return nil +} + func newCommandArgs(args []string) CommandArgs { command := map[string]string{} for i, arg := range args { @@ -306,8 +329,77 @@ func newExpectedMounts(em []string) ExpectedMounts { } } +func newMountOptions(opts []string) Options { + mountOpts := map[string]string{} + for i, o := range opts { + mountOpts[strconv.Itoa(i)] = o + } + return Options{ + Elements: mountOpts, + } +} + +// newOptionsFromConfig applies the same logic as CRI plugin to generate +// mount options given readonly and propagation config. +// TODO: (anmaxvl) update when support for other mount types is added, +// e.g., vhd:// or evd:// +// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation? +// In case we do, update securityPolicyContainer and Container structs +// as well as mount enforcement logic. +func newOptionsFromConfig(mCfg *MountConfig) []string { + mountOpts := []string{"rbind"} + + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + mountOpts = append(mountOpts, "rshared") + } else { + mountOpts = append(mountOpts, "rprivate") + } + + if mCfg.Readonly { + mountOpts = append(mountOpts, "ro") + } else { + mountOpts = append(mountOpts, "rw") + } + return mountOpts +} + +// newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI +// mount type. +func newMountTypeFromConfig(mCfg *MountConfig) string { + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + return "bind" + } + return "none" +} + +// newMountFromConfig converts user provided MountConfig into internal representation +// of mount constraint. +func newMountFromConfig(mCfg *MountConfig) Mount { + opts := newOptionsFromConfig(mCfg) + return Mount{ + Source: mCfg.HostPath, + Destination: mCfg.ContainerPath, + Type: newMountTypeFromConfig(mCfg), + Options: newMountOptions(opts), + } +} + +// newMountConstraints creates Mounts from a given array of MountConfig's. +func newMountConstraints(mountConfigs []MountConfig) Mounts { + mounts := map[string]Mount{} + for i, mc := range mountConfigs { + mounts[strconv.Itoa(i)] = newMountFromConfig(&mc) + } + return Mounts{ + Elements: mounts, + } +} + // Custom JSON marshalling to add `lenth` field that matches the number of // elements present in the `elements` field. + func (c Containers) MarshalJSON() ([]byte, error) { type Alias Containers return json.Marshal(&struct { @@ -319,46 +411,51 @@ func (c Containers) MarshalJSON() ([]byte, error) { }) } -func (l Layers) MarshalJSON() ([]byte, error) { - type Alias Layers +func (e EnvRules) MarshalJSON() ([]byte, error) { + type Alias EnvRules return json.Marshal(&struct { Length int `json:"length"` *Alias }{ - Length: len(l.Elements), - Alias: (*Alias)(&l), + Length: len(e.Elements), + Alias: (*Alias)(&e), }) } -func (c CommandArgs) MarshalJSON() ([]byte, error) { - type Alias CommandArgs +func (s StringArrayMap) MarshalJSON() ([]byte, error) { + type Alias StringArrayMap return json.Marshal(&struct { Length int `json:"length"` *Alias }{ - Length: len(c.Elements), - Alias: (*Alias)(&c), + Length: len(s.Elements), + Alias: (*Alias)(&s), }) } -func (e EnvRules) MarshalJSON() ([]byte, error) { - type Alias EnvRules - return json.Marshal(&struct { - Length int `json:"length"` - *Alias - }{ - Length: len(e.Elements), - Alias: (*Alias)(&e), - }) +func (c CommandArgs) MarshalJSON() ([]byte, error) { + return json.Marshal(StringArrayMap(c)) +} + +func (l Layers) MarshalJSON() ([]byte, error) { + return json.Marshal(StringArrayMap(l)) +} + +func (o Options) MarshalJSON() ([]byte, error) { + return json.Marshal(StringArrayMap(o)) } func (em ExpectedMounts) MarshalJSON() ([]byte, error) { - type Alias ExpectedMounts + return json.Marshal(StringArrayMap(em)) +} + +func (m Mounts) MarshalJSON() ([]byte, error) { + type Alias Mounts return json.Marshal(&struct { Length int `json:"length"` *Alias }{ - Length: len(em.Elements), - Alias: (*Alias)(&em), + Length: len(m.Elements), + Alias: (*Alias)(&m), }) } diff --git a/pkg/securitypolicy/securitypolicy_test.go b/pkg/securitypolicy/securitypolicy_test.go index 0dbe84e9f8..8a1f0b7a57 100644 --- a/pkg/securitypolicy/securitypolicy_test.go +++ b/pkg/securitypolicy/securitypolicy_test.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package securitypolicy import ( diff --git a/pkg/securitypolicy/securitypolicyenforcer.go b/pkg/securitypolicy/securitypolicyenforcer.go index 97145d819b..5b17589838 100644 --- a/pkg/securitypolicy/securitypolicyenforcer.go +++ b/pkg/securitypolicy/securitypolicyenforcer.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package securitypolicy import ( @@ -10,9 +13,10 @@ import ( "strings" "sync" + specInternal "github.com/Microsoft/hcsshim/internal/guest/spec" + "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/Microsoft/hcsshim/internal/hooks" "github.com/Microsoft/hcsshim/pkg/annotations" - "github.com/google/go-cmp/cmp" oci "github.com/opencontainers/runtime-spec/specs-go" ) @@ -22,6 +26,8 @@ type SecurityPolicyEnforcer interface { EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string, workingDir string) (err error) EnforceExpectedMountsPolicy(containerID string, spec *oci.Spec) error + EnforceMountPolicy(sandboxID, containerID string, spec *oci.Spec) error + ExtendDefaultMounts([]oci.Mount) error } func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforcer, error) { @@ -36,6 +42,45 @@ func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforce } } +type mountInternal struct { + Source string + Destination string + Type string + Options []string +} + +// newMountConstraint creates an internal mount constraint object from given +// source, destination, type and options +func newMountConstraint(src, dst string, mType string, mOpts []string) mountInternal { + return mountInternal{ + Source: src, + Destination: dst, + Type: mType, + Options: mOpts, + } +} + +// Internal version of Container +type securityPolicyContainer struct { + // The command that we will allow the container to execute + Command []string + // The rules for determining if a given environment variable is allowed + EnvRules []EnvRuleConfig + // An ordered list of dm-verity root hashes for each layer that makes up + // "a container". Containers are constructed as an overlay file system. The + // order that the layers are overlayed is important and needs to be enforced + // as part of policy. + Layers []string + // WorkingDir is a path to container's working directory, which all the processes + // will default to. + WorkingDir string + // Unordered list of mounts which are expected to be present when the container + // starts + ExpectedMounts []string `json:"expected_mounts"` + // A list of constraints for determining if a given mount is allowed. + Mounts []mountInternal +} + type StandardSecurityPolicyEnforcer struct { // EncodedSecurityPolicy state is needed for key release EncodedSecurityPolicy string @@ -104,6 +149,11 @@ type StandardSecurityPolicyEnforcer struct { startedContainers map[string]struct{} // Mutex to prevent concurrent access to fields mutex *sync.Mutex + // DefaultMounts are mount constraints for container mounts added by CRI and GCS + DefaultMounts []mountInternal + // DefaultEnvs are environment variable constraints for variables added + // by CRI and GCS + DefaultEnvs []EnvRuleConfig } var _ SecurityPolicyEnforcer = (*StandardSecurityPolicyEnforcer)(nil) @@ -133,19 +183,28 @@ func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, enc func (c Containers) toInternal() ([]securityPolicyContainer, error) { containerMapLength := len(c.Elements) if c.Length != containerMapLength { - return nil, fmt.Errorf("container numbers don't match in policy. expected: %d, actual: %d", c.Length, containerMapLength) + err := fmt.Errorf( + "container numbers don't match in policy. expected: %d, actual: %d", + c.Length, + containerMapLength, + ) + return nil, err } internal := make([]securityPolicyContainer, containerMapLength) for i := 0; i < containerMapLength; i++ { - iContainer, err := c.Elements[strconv.Itoa(i)].toInternal() + index := strconv.Itoa(i) + cConf, ok := c.Elements[index] + if !ok { + return nil, fmt.Errorf("container constraint with index %q not found", index) + } + cInternal, err := cConf.toInternal() if err != nil { return nil, err } - // save off new container - internal[i] = iContainer + internal[i] = cInternal } return internal, nil @@ -172,6 +231,10 @@ func (c Container) toInternal() (securityPolicyContainer, error) { return securityPolicyContainer{}, err } + mounts, err := c.Mounts.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } return securityPolicyContainer{ Command: command, EnvRules: envRules, @@ -180,6 +243,7 @@ func (c Container) toInternal() (securityPolicyContainer, error) { // internally and in the policy. WorkingDir: c.WorkingDir, ExpectedMounts: expectedMounts, + Mounts: mounts, }, nil } @@ -188,7 +252,7 @@ func (c CommandArgs) toInternal() ([]string, error) { return nil, fmt.Errorf("command argument numbers don't match in policy. expected: %d, actual: %d", c.Length, len(c.Elements)) } - return stringMapToStringArray(c.Elements), nil + return stringMapToStringArray(c.Elements) } func (e EnvRules) toInternal() ([]EnvRuleConfig, error) { @@ -200,9 +264,13 @@ func (e EnvRules) toInternal() ([]EnvRuleConfig, error) { envRules := make([]EnvRuleConfig, envRulesMapLength) for i := 0; i < envRulesMapLength; i++ { eIndex := strconv.Itoa(i) + elem, ok := e.Elements[eIndex] + if !ok { + return nil, fmt.Errorf("env rule with index %q doesn't exist", eIndex) + } rule := EnvRuleConfig{ - Strategy: e.Elements[eIndex].Strategy, - Rule: e.Elements[eIndex].Rule, + Strategy: elem.Strategy, + Rule: elem.Rule, } envRules[i] = rule } @@ -215,26 +283,66 @@ func (l Layers) toInternal() ([]string, error) { return nil, fmt.Errorf("layer numbers don't match in policy. expected: %d, actual: %d", l.Length, len(l.Elements)) } - return stringMapToStringArray(l.Elements), nil + return stringMapToStringArray(l.Elements) } -func (em *ExpectedMounts) toInternal() ([]string, error) { +func (em ExpectedMounts) toInternal() ([]string, error) { if em.Length != len(em.Elements) { return nil, fmt.Errorf("expectedMounts numbers don't match in policy. expected: %d, actual: %d", em.Length, len(em.Elements)) } - return stringMapToStringArray(em.Elements), nil + return stringMapToStringArray(em.Elements) } -func stringMapToStringArray(in map[string]string) []string { - inLength := len(in) - out := make([]string, inLength) +func (o Options) toInternal() ([]string, error) { + optLength := len(o.Elements) + if o.Length != optLength { + return nil, fmt.Errorf("mount option numbers don't match in policy. expected: %d, actual: %d", o.Length, optLength) + } + return stringMapToStringArray(o.Elements) +} - for i := 0; i < inLength; i++ { - out[i] = in[strconv.Itoa(i)] +func (m Mounts) toInternal() ([]mountInternal, error) { + mountLength := len(m.Elements) + if m.Length != mountLength { + return nil, fmt.Errorf("mount constraint numbers don't match in policy. expected: %d, actual: %d", m.Length, mountLength) } - return out + mountConstraints := make([]mountInternal, mountLength) + for i := 0; i < mountLength; i++ { + mIndex := strconv.Itoa(i) + mount, ok := m.Elements[mIndex] + if !ok { + return nil, fmt.Errorf("mount constraint with index %q not found", mIndex) + } + opts, err := mount.Options.toInternal() + if err != nil { + return nil, err + } + mountConstraints[i] = mountInternal{ + Source: mount.Source, + Destination: mount.Destination, + Type: mount.Type, + Options: opts, + } + } + return mountConstraints, nil +} + +func stringMapToStringArray(m map[string]string) ([]string, error) { + mapSize := len(m) + out := make([]string, mapSize) + + for i := 0; i < mapSize; i++ { + index := strconv.Itoa(i) + value, ok := m[index] + if !ok { + return nil, fmt.Errorf("element with index %q not found", index) + } + out[i] = value + } + + return out, nil } func (pe *StandardSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { @@ -358,10 +466,10 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy( } func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID string, argList []string) (err error) { - // Get a list of all the indices into our security policy's list of + // Get a list of all the indexes into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout - possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := pe.possibleIndicesForID(containerID) // Loop through every possible match and do two things: // 1- see if any command matches. we need at least one match or @@ -371,7 +479,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID strin matchingCommandFound := false for _, possibleIndex := range possibleIndices { cmd := pe.Containers[possibleIndex].Command - if cmp.Equal(cmd, argList) { + if stringSlicesEqual(cmd, argList) { matchingCommandFound = true } else { // a possible matching index turned out not to match, so we @@ -392,7 +500,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta // Get a list of all the indexes into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout and command line - possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := pe.possibleIndicesForID(containerID) for _, envVariable := range envList { matchingRuleFoundForSomeContainer := false @@ -417,7 +525,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta } func (pe *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID string, workingDir string) error { - possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := pe.possibleIndicesForID(containerID) matched := false for _, pIndex := range possibleIndices { @@ -429,7 +537,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID st } } if !matched { - return fmt.Errorf("working_dir %s unmatched by policy rule", workingDir) + return fmt.Errorf("working_dir %q unmatched by policy rule", workingDir) } return nil } @@ -453,6 +561,7 @@ func envIsMatchedByRule(envVariable string, rules []EnvRuleConfig) bool { return false } +// StandardSecurityPolicyEnforcer.mutex lock must be held prior to calling this function. func (pe *StandardSecurityPolicyEnforcer) expandMatchesForContainerIndex(index int, idToAdd string) { _, keyExists := pe.ContainerIndexToContainerIds[index] if !keyExists { @@ -462,12 +571,13 @@ func (pe *StandardSecurityPolicyEnforcer) expandMatchesForContainerIndex(index i pe.ContainerIndexToContainerIds[index][idToAdd] = struct{}{} } +// StandardSecurityPolicyEnforcer.mutex lock must be held prior to calling this function. func (pe *StandardSecurityPolicyEnforcer) narrowMatchesForContainerIndex(index int, idToRemove string) { delete(pe.ContainerIndexToContainerIds[index], idToRemove) } func equalForOverlay(a1 []string, a2 []string) bool { - // We've stored the layers from bottom to topl they are in layerPaths as + // We've stored the layers from bottom to top they are in layerPaths as // top to bottom (the order a string gets concatenated for the unix mount // command). W do our check with that in mind. if len(a1) == len(a2) { @@ -483,17 +593,135 @@ func equalForOverlay(a1 []string, a2 []string) bool { return true } -func possibleIndicesForID(containerID string, mapping map[int]map[string]struct{}) []int { - var possibles []int - for index, ids := range mapping { +// StandardSecurityPolicyEnforcer.mutex lock must be held prior to calling this function. +func (pe *StandardSecurityPolicyEnforcer) possibleIndicesForID(containerID string) []int { + var possibleIndices []int + for index, ids := range pe.ContainerIndexToContainerIds { for id := range ids { if containerID == id { - possibles = append(possibles, index) + possibleIndices = append(possibleIndices, index) } } } + return possibleIndices +} - return possibles +func (pe *StandardSecurityPolicyEnforcer) enforceDefaultMounts(specMount oci.Mount) error { + for _, mountConstraint := range pe.DefaultMounts { + if err := mountConstraint.validate(specMount); err == nil { + return nil + } + } + return fmt.Errorf("mount not allowed by default mount constraints: %+v", specMount) +} + +func (pe *StandardSecurityPolicyEnforcer) ExtendDefaultMounts(defaultMounts []oci.Mount) error { + for _, mnt := range defaultMounts { + pe.DefaultMounts = append(pe.DefaultMounts, newMountConstraint( + mnt.Source, + mnt.Destination, + mnt.Type, + mnt.Options, + )) + } + return nil +} + +// EnforceMountPolicy for StandardSecurityPolicyEnforcer validates various +// default mounts injected into container spec by GCS or containerD +func (pe *StandardSecurityPolicyEnforcer) EnforceMountPolicy(sandboxID, containerID string, spec *oci.Spec) (err error) { + pe.mutex.Lock() + defer pe.mutex.Unlock() + + possibleIndices := pe.possibleIndicesForID(containerID) + + for _, specMnt := range spec.Mounts { + // first check against default mounts + if err := pe.enforceDefaultMounts(specMnt); err == nil { + continue + } + + mountOk := false + // check against user provided mount constraints, which helps to figure + // out which container this mount spec corresponds to. + for _, pIndex := range possibleIndices { + cont := pe.Containers[pIndex] + if err = cont.matchMount(sandboxID, specMnt); err == nil { + mountOk = true + } else { + pe.narrowMatchesForContainerIndex(pIndex, containerID) + } + } + + if !mountOk { + retErr := fmt.Errorf("mount %+v is not allowed by mount constraints", specMnt) + return retErr + } + } + return nil +} + +// validate checks given OCI mount against mount policy. Destination is checked +// by direct string comparisons and Source is checked via a regular expression. +// This is done this way, because container path (Destination) is always fixed, +// however, the host/UVM path (Source) can include IDs generated at runtime and +// impossible to know in advance. +// +// NOTE: Different matching strategies can be added by introducing a separate +// path matching config, which isn't needed at the moment. +func (m *mountInternal) validate(mSpec oci.Mount) error { + if m.Type != mSpec.Type { + return fmt.Errorf("mount type not allowed by policy: expected=%q, actual=%q", m.Type, mSpec.Type) + } + if ok, _ := regexp.MatchString(m.Source, mSpec.Source); !ok { + return fmt.Errorf("mount source not allowed by policy: expected=%q, actual=%q", m.Source, mSpec.Source) + } + if m.Destination != mSpec.Destination && m.Destination != "" { + return fmt.Errorf("mount destination not allowed by policy: expected=%q, actual=%q", m.Destination, mSpec.Destination) + } + if !stringSlicesEqual(m.Options, mSpec.Options) { + return fmt.Errorf("mount options not allowed by policy: expected=%q, actual=%q", m.Options, mSpec.Options) + } + return nil +} + +// matchMount matches given OCI mount against mount constraints. If no match +// found, the mount is not allowed. +func (c *securityPolicyContainer) matchMount(sandboxID string, m oci.Mount) (err error) { + for _, constraint := range c.Mounts { + // now that we know the sandboxID we can get the actual path for + // various destination path types by adding a UVM mount prefix + constraint = substituteUVMPath(sandboxID, constraint) + if err = constraint.validate(m); err == nil { + return nil + } + } + return fmt.Errorf("mount is not allowed by policy: %+v", m) +} + +// substituteUVMPath substitutes mount prefix to an appropriate path inside +// UVM. At policy generation time, it's impossible to tell what the sandboxID +// will be, so the prefix substitution needs to happen during runtime. +func substituteUVMPath(sandboxID string, m mountInternal) mountInternal { + if strings.HasPrefix(m.Source, guestpath.SandboxMountPrefix) { + m.Source = specInternal.SandboxMountSource(sandboxID, m.Source) + } else if strings.HasPrefix(m.Source, guestpath.HugePagesMountPrefix) { + m.Source = specInternal.HugePagesMountSource(sandboxID, m.Source) + } + return m +} + +func stringSlicesEqual(slice1, slice2 []string) bool { + if len(slice1) != len(slice2) { + return false + } + + for i := 0; i < len(slice1); i++ { + if slice1[i] != slice2[i] { + return false + } + } + return true } // EnforceExpectedMountsPolicy for StandardSecurityPolicyEnforcer injects a @@ -531,7 +759,7 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(containerI } var wMounts []string - pIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + pIndices := pe.possibleIndicesForID(containerID) if len(pIndices) == 0 { return errors.New("no valid container indices found") } @@ -602,10 +830,18 @@ func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, return nil } +func (OpenDoorSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spec) error { + return nil +} + func (p *OpenDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return nil } +func (OpenDoorSecurityPolicyEnforcer) ExtendDefaultMounts(_ []oci.Mount) error { + return nil +} + type ClosedDoorSecurityPolicyEnforcer struct{} var _ SecurityPolicyEnforcer = (*ClosedDoorSecurityPolicyEnforcer)(nil) @@ -629,3 +865,11 @@ func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string func (p *ClosedDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return errors.New("enforcing expected mounts is denied by policy") } + +func (ClosedDoorSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spec) error { + return errors.New("container mounts are denied by policy") +} + +func (ClosedDoorSecurityPolicyEnforcer) ExtendDefaultMounts(_ []oci.Mount) error { + return nil +} diff --git a/test/cri-containerd/layer_integrity_test.go b/test/cri-containerd/layer_integrity_test.go index b9b8f94457..509154fe60 100644 --- a/test/cri-containerd/layer_integrity_test.go +++ b/test/cri-containerd/layer_integrity_test.go @@ -85,7 +85,7 @@ func Test_LCOW_Layer_Integrity(t *testing.T) { // Validate that verity target(s) present output := shimDiagExecOutput(ctx, t, podID, []string{"ls", "-l", "/dev/mapper"}) - filtered := filterStrings(strings.Split(output, "\n"), fmt.Sprintf("verity-%s", scenario.layerType)) + filtered := filterStrings(strings.Split(output, "\n"), fmt.Sprintf("dm-verity-%s", scenario.layerType)) if len(filtered) == 0 { t.Fatalf("expected verity targets for %s devices, none found.\n%s\n", scenario.layerType, output) } diff --git a/test/cri-containerd/policy_test.go b/test/cri-containerd/policy_test.go index e4b9421807..8ab5a6e6e3 100644 --- a/test/cri-containerd/policy_test.go +++ b/test/cri-containerd/policy_test.go @@ -20,6 +20,8 @@ var ( validPolicyAlpineCommand = []string{"ash", "-c", "echo 'Hello'"} ) +type configSideEffect func(*runtime.CreateContainerRequest) error + func securityPolicyFromContainers(containers []securitypolicy.ContainerConfig) (string, error) { pc, err := helpers.PolicyContainersFromConfigs(containers) if err != nil { @@ -51,6 +53,7 @@ func alpineSecurityPolicy(t *testing.T, opts ...securitypolicy.ContainerConfigOp securitypolicy.AuthConfig{}, "", []string{}, + []securitypolicy.MountConfig{}, ) for _, o := range opts { @@ -71,11 +74,13 @@ func sandboxRequestWithPolicy(t *testing.T, policy string) *runtime.RunPodSandbo return getRunPodSandboxRequest( t, lcowRuntimeHandler, - WithSandboxAnnotations(map[string]string{ - annotations.NoSecurityHardware: "true", - annotations.SecurityPolicy: policy, - annotations.VPMemNoMultiMapping: "true", - }), + WithSandboxAnnotations( + map[string]string{ + annotations.NoSecurityHardware: "true", + annotations.SecurityPolicy: policy, + annotations.VPMemNoMultiMapping: "true", + }, + ), ) } @@ -128,10 +133,22 @@ func Test_RunSimpleAlpineContainer_WithPolicy_Allowed(t *testing.T) { } func syncContainerConfigs(writePath, waitPath string) (writer, waiter *securitypolicy.ContainerConfig) { - writerCmdArgs := []string{"ash", "-c", fmt.Sprintf("touch %s && while true; do echo hello1; sleep 1; done", writePath)} + writerCmdArgs := []string{ + "ash", + "-c", + fmt.Sprintf( + "touch %s && while true; do echo hello1; sleep 1; done", + writePath, + )} writer = &securitypolicy.ContainerConfig{ ImageName: "alpine:latest", Command: writerCmdArgs, + Mounts: []securitypolicy.MountConfig{ + { + HostPath: "sandbox://host/path", + ContainerPath: "/mnt/shared/container-A", + }, + }, } // create container #2 that waits for a path to appear echoCmdArgs := []string{"ash", "-c", "while true; do echo hello2; sleep 1; done"} @@ -139,6 +156,12 @@ func syncContainerConfigs(writePath, waitPath string) (writer, waiter *securityp ImageName: "alpine:latest", Command: echoCmdArgs, ExpectedMounts: []string{waitPath}, + Mounts: []securitypolicy.MountConfig{ + { + HostPath: "sandbox://host/path", + ContainerPath: "/mnt/shared/container-B", + }, + }, } return writer, waiter } @@ -155,10 +178,13 @@ func syncContainerRequests( writer.Command, podConfig, ) - writerReq.Config.Mounts = append(writerReq.Config.Mounts, &runtime.Mount{ - HostPath: "sandbox://host/path", - ContainerPath: "/mnt/shared/container-A", - }) + writerReq.Config.Mounts = append( + writerReq.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://host/path", + ContainerPath: "/mnt/shared/container-A", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) waiterReq = getCreateContainerRequest( podID, @@ -167,11 +193,14 @@ func syncContainerRequests( waiter.Command, podConfig, ) - waiterReq.Config.Mounts = append(waiterReq.Config.Mounts, &runtime.Mount{ - // The HostPath must be the same as for the "writer" container - HostPath: "sandbox://host/path", - ContainerPath: "/mnt/shared/container-B", - }) + waiterReq.Config.Mounts = append( + waiterReq.Config.Mounts, &runtime.Mount{ + // The HostPath must be the same as for the "writer" container + HostPath: "sandbox://host/path", + ContainerPath: "/mnt/shared/container-B", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) return writerReq, waiterReq } @@ -180,14 +209,14 @@ func Test_RunContainers_WithSyncHooks_ValidWaitPath(t *testing.T) { requireFeatures(t, featureLCOW, featureLCOWIntegrity) writerCfg, waiterCfg := syncContainerConfigs( - "/mnt/shared/container-A/sync-file", "/mnt/shared/container-B/sync-file") + "/mnt/shared/container-A/sync-file", "/mnt/shared/container-B/sync-file", + ) containerConfigs := append(helpers.DefaultContainerConfigs(), *writerCfg, *waiterCfg) policyString, err := securityPolicyFromContainers(containerConfigs) if err != nil { t.Fatalf("failed to generate security policy string: %s", err) } - client := newTestRuntimeClient(t) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -244,9 +273,11 @@ func Test_RunContainers_WithSyncHooks_InvalidWaitPath(t *testing.T) { defer removeContainer(t, client, ctx, cidWriter) defer stopContainer(t, client, ctx, cidWriter) - _, err = client.StartContainer(ctx, &runtime.StartContainerRequest{ - ContainerId: cidWaiter, - }) + _, err = client.StartContainer( + ctx, &runtime.StartContainerRequest{ + ContainerId: cidWaiter, + }, + ) expectedErrString := "timeout while waiting for path" if err == nil { defer removeContainer(t, client, ctx, cidWaiter) @@ -285,10 +316,12 @@ func Test_RunContainer_ValidContainerConfigs_Allowed(t *testing.T) { { name: "EnvironmentVariable", sf: func(req *runtime.CreateContainerRequest) { - req.Config.Envs = append(req.Config.Envs, &runtime.KeyValue{ - Key: "KEY", - Value: "VALUE", - }) + req.Config.Envs = append( + req.Config.Envs, &runtime.KeyValue{ + Key: "KEY", + Value: "VALUE", + }, + ) }, opts: []securitypolicy.ContainerConfigOpt{ securitypolicy.WithEnvVarRules( @@ -297,7 +330,8 @@ func Test_RunContainer_ValidContainerConfigs_Allowed(t *testing.T) { Strategy: securitypolicy.EnvVarRuleString, Rule: "KEY=VALUE", }, - }), + }, + ), }, }, } { @@ -327,10 +361,9 @@ func Test_RunContainer_ValidContainerConfigs_Allowed(t *testing.T) { } func Test_RunContainer_InvalidContainerConfigs_NotAllowed(t *testing.T) { - type sideEffect func(*runtime.CreateContainerRequest) type config struct { name string - sf sideEffect + sf configSideEffect expectedError string } @@ -345,25 +378,30 @@ func Test_RunContainer_InvalidContainerConfigs_NotAllowed(t *testing.T) { for _, testConfig := range []config{ { name: "InvalidWorkingDir", - sf: func(req *runtime.CreateContainerRequest) { + sf: func(req *runtime.CreateContainerRequest) error { req.Config.WorkingDir = "/non/existent" + return nil }, expectedError: "working_dir /non/existent unmatched by policy rule", }, { name: "InvalidCommand", - sf: func(req *runtime.CreateContainerRequest) { + sf: func(req *runtime.CreateContainerRequest) error { req.Config.Command = []string{"ash", "-c", "echo 'invalid command'"} + return nil }, expectedError: "command [ash -c echo 'invalid command'] doesn't match policy", }, { name: "InvalidEnvironmentVariable", - sf: func(req *runtime.CreateContainerRequest) { - req.Config.Envs = append(req.Config.Envs, &runtime.KeyValue{ - Key: "KEY", - Value: "VALUE", - }) + sf: func(req *runtime.CreateContainerRequest) error { + req.Config.Envs = append( + req.Config.Envs, &runtime.KeyValue{ + Key: "KEY", + Value: "VALUE", + }, + ) + return nil }, expectedError: "env variable KEY=VALUE unmatched by policy rule", }, @@ -382,12 +420,292 @@ func Test_RunContainer_InvalidContainerConfigs_NotAllowed(t *testing.T) { validPolicyAlpineCommand, sandboxRequest.Config, ) - testConfig.sf(containerRequest) + + if err := testConfig.sf(containerRequest); err != nil { + t.Fatalf("failed to apply containerRequest side effect: %s", err) + } + + containerID := createContainer(t, client, ctx, containerRequest) + _, err := client.StartContainer( + ctx, &runtime.StartContainerRequest{ + ContainerId: containerID, + }, + ) + if err == nil { + t.Fatal("expected container start failure") + } + if !strings.Contains(err.Error(), testConfig.expectedError) { + t.Fatalf("expected %q in error message, got: %q", testConfig.expectedError, err) + } + }) + } +} + +func Test_RunContainer_WithMountConstraints_Allowed(t *testing.T) { + requireFeatures(t, featureLCOW, featureLCOWIntegrity) + pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine}) + + client := newTestRuntimeClient(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + type config struct { + name string + sideEffect configSideEffect + opts []securitypolicy.ContainerConfigOpt + } + + for _, testConfig := range []config{ + { + name: "DefaultMounts", + sideEffect: func(_ *runtime.CreateContainerRequest) error { + return nil + }, + opts: []securitypolicy.ContainerConfigOpt{}, + }, + { + name: "SandboxMountRW", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) + return nil + }, + opts: []securitypolicy.ContainerConfigOpt{ + securitypolicy.WithMountConstraints( + []securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + }, + }, + )}, + }, + { + name: "SandboxMountRO", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + Readonly: true, + }, + ) + return nil + }, + opts: []securitypolicy.ContainerConfigOpt{ + securitypolicy.WithMountConstraints( + []securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Readonly: true, + }, + }, + )}, + }, + { + name: "SandboxMountRegex", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path/regexp", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) + return nil + }, + opts: []securitypolicy.ContainerConfigOpt{ + securitypolicy.WithMountConstraints( + []securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path/r.+", + ContainerPath: "/container/path", + }, + }, + )}, + }, + } { + t.Run(testConfig.name, func(t *testing.T) { + alpinePolicy := alpineSecurityPolicy(t, testConfig.opts...) + sandboxRequest := sandboxRequestWithPolicy(t, alpinePolicy) + + podID := runPodSandbox(t, client, ctx, sandboxRequest) + defer removePodSandbox(t, client, ctx, podID) + defer stopPodSandbox(t, client, ctx, podID) + + containerRequest := getCreateContainerRequest( + podID, + "alpine-with-policy", + "alpine:latest", + validPolicyAlpineCommand, + sandboxRequest.Config, + ) + + if err := testConfig.sideEffect(containerRequest); err != nil { + t.Fatalf("failed to apply containerRequest side effect: %s", err) + } + + containerID := createContainer(t, client, ctx, containerRequest) + startContainer(t, client, ctx, containerID) + defer removeContainer(t, client, ctx, containerID) + defer stopContainer(t, client, ctx, containerID) + }) + } +} + +func Test_RunContainer_WithMountConstraints_NotAllowed(t *testing.T) { + requireFeatures(t, featureLCOW, featureLCOWIntegrity) + pullRequiredLCOWImages(t, []string{imageLcowK8sPause, imageLcowAlpine}) + + client := newTestRuntimeClient(t) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + type config struct { + name string + sideEffect configSideEffect + opts []securitypolicy.ContainerConfigOpt + expectedError string + } + + testSandboxMountOpts := []securitypolicy.ContainerConfigOpt{ + securitypolicy.WithMountConstraints( + []securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + }, + }, + ), + } + for _, testConfig := range []config{ + { + name: "InvalidSandboxMountSource", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/invalid/path", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) + return nil + }, + opts: testSandboxMountOpts, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidSandboxMountDestination", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path/invalid", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) + return nil + }, + opts: testSandboxMountOpts, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidSandboxMountFlagRO", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + Readonly: true, + }, + ) + return nil + }, + opts: testSandboxMountOpts, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidSandboxMountFlagRW", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) + return nil + }, + opts: []securitypolicy.ContainerConfigOpt{ + securitypolicy.WithMountConstraints( + []securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path", + ContainerPath: "/container/path", + Readonly: true, + }, + }, + )}, + expectedError: "is not allowed by mount constraints", + }, + { + name: "InvalidHostPathForRegex", + sideEffect: func(req *runtime.CreateContainerRequest) error { + req.Config.Mounts = append( + req.Config.Mounts, &runtime.Mount{ + HostPath: "sandbox://sandbox/path/regex/no/match", + ContainerPath: "/container/path", + Propagation: runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL, + }, + ) + return nil + }, + opts: []securitypolicy.ContainerConfigOpt{ + securitypolicy.WithMountConstraints( + []securitypolicy.MountConfig{ + { + HostPath: "sandbox://sandbox/path/R.+", + ContainerPath: "/container/path", + }, + }, + )}, + expectedError: "is not allowed by mount constraints", + }, + } { + t.Run(testConfig.name, func(t *testing.T) { + alpinePolicy := alpineSecurityPolicy(t, testConfig.opts...) + sandboxRequest := sandboxRequestWithPolicy(t, alpinePolicy) + + podID := runPodSandbox(t, client, ctx, sandboxRequest) + defer removePodSandbox(t, client, ctx, podID) + defer stopPodSandbox(t, client, ctx, podID) + + containerRequest := getCreateContainerRequest( + podID, + "alpine-with-policy", + "alpine:latest", + validPolicyAlpineCommand, + sandboxRequest.Config, + ) + + if err := testConfig.sideEffect(containerRequest); err != nil { + t.Fatalf("failed to apply containerRequest side effect: %s", err) + } containerID := createContainer(t, client, ctx, containerRequest) - _, err := client.StartContainer(ctx, &runtime.StartContainerRequest{ - ContainerId: containerID, - }) + _, err := client.StartContainer( + ctx, &runtime.StartContainerRequest{ + ContainerId: containerID, + }, + ) if err == nil { t.Fatal("expected container start failure") } diff --git a/test/go.mod b/test/go.mod index e8c9ef823a..d15a83220c 100644 --- a/test/go.mod +++ b/test/go.mod @@ -38,7 +38,6 @@ require ( github.com/gogo/googleapis v1.4.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.0 // indirect - github.com/google/go-cmp v0.5.6 // indirect github.com/google/go-containerregistry v0.5.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go b/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go new file mode 100644 index 0000000000..842e1822c4 --- /dev/null +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/guest/spec/spec.go @@ -0,0 +1,92 @@ +//go:build linux +// +build linux + +// Package spec encapsulates a number of GCS specific oci spec modifications, e.g., +// networking mounts, sandbox path substitutions in guest etc. +// +// TODO: consider moving oci spec specific code from /internal/guest/runtime/hcsv2/spec.go +package spec + +import ( + "path/filepath" + "strings" + + "github.com/Microsoft/hcsshim/internal/guestpath" + oci "github.com/opencontainers/runtime-spec/specs-go" +) + +// networkingMountPaths returns an array of mount paths to enable networking +// inside containers. +func networkingMountPaths() []string { + return []string{ + "/etc/hostname", + "/etc/hosts", + "/etc/resolv.conf", + } +} + +// GenerateWorkloadContainerNetworkMounts generates an array of specs.Mount +// required for container networking. Original spec is left untouched and +// it's the responsibility of a caller to update it. +func GenerateWorkloadContainerNetworkMounts(sandboxID string, spec *oci.Spec) []oci.Mount { + var nMounts []oci.Mount + + for _, mountPath := range networkingMountPaths() { + // Don't override if the mount is present in the spec + if MountPresent(mountPath, spec.Mounts) { + continue + } + options := []string{"bind"} + if spec.Root != nil && spec.Root.Readonly { + options = append(options, "ro") + } + trimmedMountPath := strings.TrimPrefix(mountPath, "/etc/") + mt := oci.Mount{ + Destination: mountPath, + Type: "bind", + Source: filepath.Join(SandboxRootDir(sandboxID), trimmedMountPath), + Options: options, + } + nMounts = append(nMounts, mt) + } + return nMounts +} + +// MountPresent checks if mountPath is present in the specMounts array. +func MountPresent(mountPath string, specMounts []oci.Mount) bool { + for _, m := range specMounts { + if m.Destination == mountPath { + return true + } + } + return false +} + +// SandboxRootDir returns the sandbox container root directory inside UVM/host. +func SandboxRootDir(sandboxID string) string { + return filepath.Join(guestpath.LCOWRootPrefixInUVM, sandboxID) +} + +// SandboxMountsDir returns sandbox mounts directory inside UVM/host. +func SandboxMountsDir(sandboxID string) string { + return filepath.Join(SandboxRootDir(sandboxID), "sandboxMounts") +} + +// HugePagesMountsDir returns hugepages mounts directory inside UVM. +func HugePagesMountsDir(sandboxID string) string { + return filepath.Join(SandboxRootDir(sandboxID), "hugepages") +} + +// SandboxMountSource returns sandbox mount path inside UVM +func SandboxMountSource(sandboxID, path string) string { + mountsDir := SandboxMountsDir(sandboxID) + subPath := strings.TrimPrefix(path, guestpath.SandboxMountPrefix) + return filepath.Join(mountsDir, subPath) +} + +// HugePagesMountSource returns hugepages mount path inside UVM +func HugePagesMountSource(sandboxID, path string) string { + mountsDir := HugePagesMountsDir(sandboxID) + subPath := strings.TrimPrefix(path, guestpath.HugePagesMountPrefix) + return filepath.Join(mountsDir, subPath) +} diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/guestpath/paths.go b/test/vendor/github.com/Microsoft/hcsshim/internal/guestpath/paths.go index 62bbfeb636..be812ba075 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/guestpath/paths.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/guestpath/paths.go @@ -1,23 +1,29 @@ package guestpath const ( - // LCOWNvidiaMountPath is the path format in LCOW UVM where nvidia tools are mounted - // keep this value in sync with opengcs + // LCOWNvidiaMountPath is the path format in LCOW UVM where nvidia tools + // are mounted keep this value in sync with opengcs LCOWNvidiaMountPath = "/run/nvidia" - // LCOWRootPrefixInUVM is the path inside UVM where LCOW container's root file system will be mounted + // LCOWRootPrefixInUVM is the path inside UVM where LCOW container's root + // file system will be mounted LCOWRootPrefixInUVM = "/run/gcs/c" - // WCOWRootPrefixInUVM is the path inside UVM where WCOW container's root file system will be mounted + // WCOWRootPrefixInUVM is the path inside UVM where WCOW container's root + // file system will be mounted WCOWRootPrefixInUVM = `C:\c` - // SandboxMountPrefix is mount prefix used in container spec to mark a sandbox-mount + // SandboxMountPrefix is mount prefix used in container spec to mark a + // sandbox-mount SandboxMountPrefix = "sandbox://" - // HugePagesMountPrefix is mount prefix used in container spec to mark a huge-pages mount + // HugePagesMountPrefix is mount prefix used in container spec to mark a + // huge-pages mount HugePagesMountPrefix = "hugepages://" - // LCOWMountPathPrefixFmt is the path format in the LCOW UVM where non global mounts, such - // as Plan9 mounts are added + // LCOWMountPathPrefixFmt is the path format in the LCOW UVM where + // non-global mounts, such as Plan9 mounts are added LCOWMountPathPrefixFmt = "/mounts/m%d" - // LCOWGlobalMountPrefixFmt is the path format in the LCOW UVM where global mounts are added + // LCOWGlobalMountPrefixFmt is the path format in the LCOW UVM where global + // mounts are added LCOWGlobalMountPrefixFmt = "/run/mounts/m%d" - // WCOWGlobalMountPrefixFmt is the path prefix format in the WCOW UVM where mounts are added + // WCOWGlobalMountPrefixFmt is the path prefix format in the WCOW UVM where + // mounts are added WCOWGlobalMountPrefixFmt = "C:\\mounts\\m%d" // RootfsPath is part of the container's rootfs path RootfsPath = "rootfs" diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go b/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go index 103b2bae3a..9170bec6ae 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers/helpers.go @@ -75,6 +75,7 @@ func DefaultContainerConfigs() []securitypolicy.ContainerConfig { securitypolicy.AuthConfig{}, "", []string{}, + []securitypolicy.MountConfig{}, ) return []securitypolicy.ContainerConfig{pause} } @@ -137,12 +138,13 @@ func PolicyContainersFromConfigs(containerConfigs []securitypolicy.ContainerConf workingDir = containerConfig.WorkingDir } - container, err := securitypolicy.NewContainer( + container, err := securitypolicy.CreateContainerPolicy( containerConfig.Command, layerHashes, envRules, workingDir, containerConfig.ExpectedMounts, + containerConfig.Mounts, ) if err != nil { return nil, err diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go index 32885ce666..5d64296fba 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/opts.go @@ -25,3 +25,12 @@ func WithWorkingDir(wd string) ContainerConfigOpt { return nil } } + +// WithMountConstraints extends ContainerConfig.Mounts with provided mount +// constraints. +func WithMountConstraints(mc []MountConfig) ContainerConfigOpt { + return func(c *ContainerConfig) error { + c.Mounts = append(c.Mounts, mc...) + return nil + } +} diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go index a75ada7030..c9cdf3226f 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -7,7 +7,9 @@ import ( "fmt" "regexp" "strconv" + "strings" + "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/pkg/errors" ) @@ -46,6 +48,15 @@ type ContainerConfig struct { EnvRules []EnvRuleConfig `json:"env_rules" toml:"env_rule"` WorkingDir string `json:"working_dir" toml:"working_dir"` ExpectedMounts []string `json:"expected_mounts" toml:"expected_mounts"` + Mounts []MountConfig `json:"mounts" toml:"mount"` +} + +// MountConfig contains toml or JSON config for mount security policy +// constraint description. +type MountConfig struct { + HostPath string `json:"host_path" toml:"host_path"` + ContainerPath string `json:"container_path" toml:"container_path"` + Readonly bool `json:"readonly" toml:"readonly"` } // NewContainerConfig creates a new ContainerConfig from the given values. @@ -56,6 +67,7 @@ func NewContainerConfig( auth AuthConfig, workingDir string, expectedMounts []string, + mounts []MountConfig, ) ContainerConfig { return ContainerConfig{ ImageName: imageName, @@ -64,6 +76,7 @@ func NewContainerConfig( Auth: auth, WorkingDir: workingDir, ExpectedMounts: expectedMounts, + Mounts: mounts, } } @@ -101,25 +114,6 @@ func NewSecurityPolicyDigest(base64policy string) ([]byte, error) { return digestBytes, nil } -// Internal version of SecurityPolicyContainer -type securityPolicyContainer struct { - // The command that we will allow the container to execute - Command []string - // The rules for determining if a given environment variable is allowed - EnvRules []EnvRuleConfig - // An ordered list of dm-verity root hashes for each layer that makes up - // "a container". Containers are constructed as an overlay file system. The - // order that the layers are overlayed is important and needs to be enforced - // as part of policy. - Layers []string - // WorkingDir is a path to container's working directory, which all the processes - // will default to. - WorkingDir string - // Unordered list of mounts which are expected to be present when the container - // starts - ExpectedMounts []string `json:"expected_mounts"` -} - // SecurityPolicyState is a structure that holds user supplied policy to enforce // we keep both the encoded representation and the unmarshalled representation // because different components need to have access to either of these @@ -135,12 +129,12 @@ type EncodedSecurityPolicy struct { SecurityPolicy string `json:"SecurityPolicy,omitempty"` } -// Constructs SecurityPolicyState from base64Policy string. It first decodes -// base64 policy and returns the structs security policy struct and encoded -// security policy for given policy. The security policy is transmitted as json -// in an annotation, so we first have to remove the base64 encoding that allows -// the JSON based policy to be passed as a string. From there, we decode the -// JSON and setup our security policy struct +// NewSecurityPolicyState constructs SecurityPolicyState from base64Policy +// string. It first decodes base64 policy and returns the security policy +// struct and encoded security policy for given policy. The security policy +// is transmitted as json in an annotation, so we first have to remove the +// base64 encoding that allows the JSON based policy to be passed as a string. +// From there, we decode the JSON and set up our security policy struct func NewSecurityPolicyState(base64Policy string) (*SecurityPolicyState, error) { // construct an encoded security policy that holds the base64 representation encodedSecurityPolicy := EncodedSecurityPolicy{ @@ -170,12 +164,12 @@ func NewSecurityPolicyState(base64Policy string) (*SecurityPolicyState, error) { } type SecurityPolicy struct { - // Flag that when set to true allows for all checks to pass. Currently used + // Flag that when set to true allows for all checks to pass. Currently, used // to run with security policy enforcement "running dark"; checks can be in // place but the default policy that is created on startup has AllowAll set // to true, thus making policy enforcement effectively "off" from a logical // standpoint. Policy enforcement isn't actually off as the policy is "allow - // everything:. + // everything". AllowAll bool `json:"allow_all"` // One or more containers that are allowed to run Containers Containers `json:"containers"` @@ -201,42 +195,62 @@ type Container struct { Layers Layers `json:"layers"` WorkingDir string `json:"working_dir"` ExpectedMounts ExpectedMounts `json:"expected_mounts"` + Mounts Mounts `json:"mounts"` } -type Layers struct { - Length int `json:"length"` - // an ordered list of args where the key is in the index for ordering +// StringArrayMap wraps an array of strings as a string map. +type StringArrayMap struct { + Length int `json:"length"` Elements map[string]string `json:"elements"` } -type CommandArgs struct { - Length int `json:"length"` - // an ordered list of args where the key is in the index for ordering - Elements map[string]string `json:"elements"` -} +type Layers StringArrayMap + +type CommandArgs StringArrayMap + +type ExpectedMounts StringArrayMap + +type Options StringArrayMap type EnvRules struct { Length int `json:"length"` Elements map[string]EnvRuleConfig `json:"elements"` } -type ExpectedMounts struct { - Length int `json:"length"` - Elements map[string]string `json:"elements"` +type Mount struct { + Source string `json:"source"` + Destination string `json:"destination"` + Type string `json:"type"` + Options Options `json:"options"` +} + +type Mounts struct { + Length int `json:"length"` + Elements map[string]Mount `json:"elements"` } -// NewContainer creates a new Container instance from the provided values -// or an error if envRules validation fails. -func NewContainer(command, layers []string, envRules []EnvRuleConfig, workingDir string, eMounts []string) (*Container, error) { +// CreateContainerPolicy creates a new Container policy instance from the +// provided constraints or an error if parameter validation fails. +func CreateContainerPolicy( + command, layers []string, + envRules []EnvRuleConfig, + workingDir string, + eMounts []string, + mounts []MountConfig, +) (*Container, error) { if err := validateEnvRules(envRules); err != nil { return nil, err } + if err := validateMountConstraint(mounts); err != nil { + return nil, err + } return &Container{ Command: newCommandArgs(command), Layers: newLayers(layers), EnvRules: newEnvRules(envRules), WorkingDir: workingDir, ExpectedMounts: newExpectedMounts(eMounts), + Mounts: newMountConstraints(mounts), }, nil } @@ -266,6 +280,15 @@ func validateEnvRules(rules []EnvRuleConfig) error { return nil } +func validateMountConstraint(mounts []MountConfig) error { + for _, m := range mounts { + if _, err := regexp.Compile(m.HostPath); err != nil { + return err + } + } + return nil +} + func newCommandArgs(args []string) CommandArgs { command := map[string]string{} for i, arg := range args { @@ -306,8 +329,77 @@ func newExpectedMounts(em []string) ExpectedMounts { } } +func newMountOptions(opts []string) Options { + mountOpts := map[string]string{} + for i, o := range opts { + mountOpts[strconv.Itoa(i)] = o + } + return Options{ + Elements: mountOpts, + } +} + +// newOptionsFromConfig applies the same logic as CRI plugin to generate +// mount options given readonly and propagation config. +// TODO: (anmaxvl) update when support for other mount types is added, +// e.g., vhd:// or evd:// +// TODO: (anmaxvl) Do we need to set/validate Linux rootfs propagation? +// In case we do, update securityPolicyContainer and Container structs +// as well as mount enforcement logic. +func newOptionsFromConfig(mCfg *MountConfig) []string { + mountOpts := []string{"rbind"} + + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + mountOpts = append(mountOpts, "rshared") + } else { + mountOpts = append(mountOpts, "rprivate") + } + + if mCfg.Readonly { + mountOpts = append(mountOpts, "ro") + } else { + mountOpts = append(mountOpts, "rw") + } + return mountOpts +} + +// newMountTypeFromConfig mimics the behavior in CRI when figuring out OCI +// mount type. +func newMountTypeFromConfig(mCfg *MountConfig) string { + if strings.HasPrefix(mCfg.HostPath, guestpath.SandboxMountPrefix) || + strings.HasPrefix(mCfg.HostPath, guestpath.HugePagesMountPrefix) { + return "bind" + } + return "none" +} + +// newMountFromConfig converts user provided MountConfig into internal representation +// of mount constraint. +func newMountFromConfig(mCfg *MountConfig) Mount { + opts := newOptionsFromConfig(mCfg) + return Mount{ + Source: mCfg.HostPath, + Destination: mCfg.ContainerPath, + Type: newMountTypeFromConfig(mCfg), + Options: newMountOptions(opts), + } +} + +// newMountConstraints creates Mounts from a given array of MountConfig's. +func newMountConstraints(mountConfigs []MountConfig) Mounts { + mounts := map[string]Mount{} + for i, mc := range mountConfigs { + mounts[strconv.Itoa(i)] = newMountFromConfig(&mc) + } + return Mounts{ + Elements: mounts, + } +} + // Custom JSON marshalling to add `lenth` field that matches the number of // elements present in the `elements` field. + func (c Containers) MarshalJSON() ([]byte, error) { type Alias Containers return json.Marshal(&struct { @@ -319,46 +411,51 @@ func (c Containers) MarshalJSON() ([]byte, error) { }) } -func (l Layers) MarshalJSON() ([]byte, error) { - type Alias Layers +func (e EnvRules) MarshalJSON() ([]byte, error) { + type Alias EnvRules return json.Marshal(&struct { Length int `json:"length"` *Alias }{ - Length: len(l.Elements), - Alias: (*Alias)(&l), + Length: len(e.Elements), + Alias: (*Alias)(&e), }) } -func (c CommandArgs) MarshalJSON() ([]byte, error) { - type Alias CommandArgs +func (s StringArrayMap) MarshalJSON() ([]byte, error) { + type Alias StringArrayMap return json.Marshal(&struct { Length int `json:"length"` *Alias }{ - Length: len(c.Elements), - Alias: (*Alias)(&c), + Length: len(s.Elements), + Alias: (*Alias)(&s), }) } -func (e EnvRules) MarshalJSON() ([]byte, error) { - type Alias EnvRules - return json.Marshal(&struct { - Length int `json:"length"` - *Alias - }{ - Length: len(e.Elements), - Alias: (*Alias)(&e), - }) +func (c CommandArgs) MarshalJSON() ([]byte, error) { + return json.Marshal(StringArrayMap(c)) +} + +func (l Layers) MarshalJSON() ([]byte, error) { + return json.Marshal(StringArrayMap(l)) +} + +func (o Options) MarshalJSON() ([]byte, error) { + return json.Marshal(StringArrayMap(o)) } func (em ExpectedMounts) MarshalJSON() ([]byte, error) { - type Alias ExpectedMounts + return json.Marshal(StringArrayMap(em)) +} + +func (m Mounts) MarshalJSON() ([]byte, error) { + type Alias Mounts return json.Marshal(&struct { Length int `json:"length"` *Alias }{ - Length: len(em.Elements), - Alias: (*Alias)(&em), + Length: len(m.Elements), + Alias: (*Alias)(&m), }) } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go index 97145d819b..5b17589838 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go @@ -1,3 +1,6 @@ +//go:build linux +// +build linux + package securitypolicy import ( @@ -10,9 +13,10 @@ import ( "strings" "sync" + specInternal "github.com/Microsoft/hcsshim/internal/guest/spec" + "github.com/Microsoft/hcsshim/internal/guestpath" "github.com/Microsoft/hcsshim/internal/hooks" "github.com/Microsoft/hcsshim/pkg/annotations" - "github.com/google/go-cmp/cmp" oci "github.com/opencontainers/runtime-spec/specs-go" ) @@ -22,6 +26,8 @@ type SecurityPolicyEnforcer interface { EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) EnforceCreateContainerPolicy(containerID string, argList []string, envList []string, workingDir string) (err error) EnforceExpectedMountsPolicy(containerID string, spec *oci.Spec) error + EnforceMountPolicy(sandboxID, containerID string, spec *oci.Spec) error + ExtendDefaultMounts([]oci.Mount) error } func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforcer, error) { @@ -36,6 +42,45 @@ func NewSecurityPolicyEnforcer(state SecurityPolicyState) (SecurityPolicyEnforce } } +type mountInternal struct { + Source string + Destination string + Type string + Options []string +} + +// newMountConstraint creates an internal mount constraint object from given +// source, destination, type and options +func newMountConstraint(src, dst string, mType string, mOpts []string) mountInternal { + return mountInternal{ + Source: src, + Destination: dst, + Type: mType, + Options: mOpts, + } +} + +// Internal version of Container +type securityPolicyContainer struct { + // The command that we will allow the container to execute + Command []string + // The rules for determining if a given environment variable is allowed + EnvRules []EnvRuleConfig + // An ordered list of dm-verity root hashes for each layer that makes up + // "a container". Containers are constructed as an overlay file system. The + // order that the layers are overlayed is important and needs to be enforced + // as part of policy. + Layers []string + // WorkingDir is a path to container's working directory, which all the processes + // will default to. + WorkingDir string + // Unordered list of mounts which are expected to be present when the container + // starts + ExpectedMounts []string `json:"expected_mounts"` + // A list of constraints for determining if a given mount is allowed. + Mounts []mountInternal +} + type StandardSecurityPolicyEnforcer struct { // EncodedSecurityPolicy state is needed for key release EncodedSecurityPolicy string @@ -104,6 +149,11 @@ type StandardSecurityPolicyEnforcer struct { startedContainers map[string]struct{} // Mutex to prevent concurrent access to fields mutex *sync.Mutex + // DefaultMounts are mount constraints for container mounts added by CRI and GCS + DefaultMounts []mountInternal + // DefaultEnvs are environment variable constraints for variables added + // by CRI and GCS + DefaultEnvs []EnvRuleConfig } var _ SecurityPolicyEnforcer = (*StandardSecurityPolicyEnforcer)(nil) @@ -133,19 +183,28 @@ func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, enc func (c Containers) toInternal() ([]securityPolicyContainer, error) { containerMapLength := len(c.Elements) if c.Length != containerMapLength { - return nil, fmt.Errorf("container numbers don't match in policy. expected: %d, actual: %d", c.Length, containerMapLength) + err := fmt.Errorf( + "container numbers don't match in policy. expected: %d, actual: %d", + c.Length, + containerMapLength, + ) + return nil, err } internal := make([]securityPolicyContainer, containerMapLength) for i := 0; i < containerMapLength; i++ { - iContainer, err := c.Elements[strconv.Itoa(i)].toInternal() + index := strconv.Itoa(i) + cConf, ok := c.Elements[index] + if !ok { + return nil, fmt.Errorf("container constraint with index %q not found", index) + } + cInternal, err := cConf.toInternal() if err != nil { return nil, err } - // save off new container - internal[i] = iContainer + internal[i] = cInternal } return internal, nil @@ -172,6 +231,10 @@ func (c Container) toInternal() (securityPolicyContainer, error) { return securityPolicyContainer{}, err } + mounts, err := c.Mounts.toInternal() + if err != nil { + return securityPolicyContainer{}, err + } return securityPolicyContainer{ Command: command, EnvRules: envRules, @@ -180,6 +243,7 @@ func (c Container) toInternal() (securityPolicyContainer, error) { // internally and in the policy. WorkingDir: c.WorkingDir, ExpectedMounts: expectedMounts, + Mounts: mounts, }, nil } @@ -188,7 +252,7 @@ func (c CommandArgs) toInternal() ([]string, error) { return nil, fmt.Errorf("command argument numbers don't match in policy. expected: %d, actual: %d", c.Length, len(c.Elements)) } - return stringMapToStringArray(c.Elements), nil + return stringMapToStringArray(c.Elements) } func (e EnvRules) toInternal() ([]EnvRuleConfig, error) { @@ -200,9 +264,13 @@ func (e EnvRules) toInternal() ([]EnvRuleConfig, error) { envRules := make([]EnvRuleConfig, envRulesMapLength) for i := 0; i < envRulesMapLength; i++ { eIndex := strconv.Itoa(i) + elem, ok := e.Elements[eIndex] + if !ok { + return nil, fmt.Errorf("env rule with index %q doesn't exist", eIndex) + } rule := EnvRuleConfig{ - Strategy: e.Elements[eIndex].Strategy, - Rule: e.Elements[eIndex].Rule, + Strategy: elem.Strategy, + Rule: elem.Rule, } envRules[i] = rule } @@ -215,26 +283,66 @@ func (l Layers) toInternal() ([]string, error) { return nil, fmt.Errorf("layer numbers don't match in policy. expected: %d, actual: %d", l.Length, len(l.Elements)) } - return stringMapToStringArray(l.Elements), nil + return stringMapToStringArray(l.Elements) } -func (em *ExpectedMounts) toInternal() ([]string, error) { +func (em ExpectedMounts) toInternal() ([]string, error) { if em.Length != len(em.Elements) { return nil, fmt.Errorf("expectedMounts numbers don't match in policy. expected: %d, actual: %d", em.Length, len(em.Elements)) } - return stringMapToStringArray(em.Elements), nil + return stringMapToStringArray(em.Elements) } -func stringMapToStringArray(in map[string]string) []string { - inLength := len(in) - out := make([]string, inLength) +func (o Options) toInternal() ([]string, error) { + optLength := len(o.Elements) + if o.Length != optLength { + return nil, fmt.Errorf("mount option numbers don't match in policy. expected: %d, actual: %d", o.Length, optLength) + } + return stringMapToStringArray(o.Elements) +} - for i := 0; i < inLength; i++ { - out[i] = in[strconv.Itoa(i)] +func (m Mounts) toInternal() ([]mountInternal, error) { + mountLength := len(m.Elements) + if m.Length != mountLength { + return nil, fmt.Errorf("mount constraint numbers don't match in policy. expected: %d, actual: %d", m.Length, mountLength) } - return out + mountConstraints := make([]mountInternal, mountLength) + for i := 0; i < mountLength; i++ { + mIndex := strconv.Itoa(i) + mount, ok := m.Elements[mIndex] + if !ok { + return nil, fmt.Errorf("mount constraint with index %q not found", mIndex) + } + opts, err := mount.Options.toInternal() + if err != nil { + return nil, err + } + mountConstraints[i] = mountInternal{ + Source: mount.Source, + Destination: mount.Destination, + Type: mount.Type, + Options: opts, + } + } + return mountConstraints, nil +} + +func stringMapToStringArray(m map[string]string) ([]string, error) { + mapSize := len(m) + out := make([]string, mapSize) + + for i := 0; i < mapSize; i++ { + index := strconv.Itoa(i) + value, ok := m[index] + if !ok { + return nil, fmt.Errorf("element with index %q not found", index) + } + out[i] = value + } + + return out, nil } func (pe *StandardSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { @@ -358,10 +466,10 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceCreateContainerPolicy( } func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID string, argList []string) (err error) { - // Get a list of all the indices into our security policy's list of + // Get a list of all the indexes into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout - possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := pe.possibleIndicesForID(containerID) // Loop through every possible match and do two things: // 1- see if any command matches. we need at least one match or @@ -371,7 +479,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceCommandPolicy(containerID strin matchingCommandFound := false for _, possibleIndex := range possibleIndices { cmd := pe.Containers[possibleIndex].Command - if cmp.Equal(cmd, argList) { + if stringSlicesEqual(cmd, argList) { matchingCommandFound = true } else { // a possible matching index turned out not to match, so we @@ -392,7 +500,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta // Get a list of all the indexes into our security policy's list of // containers that are possible matches for this containerID based // on the image overlay layout and command line - possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := pe.possibleIndicesForID(containerID) for _, envVariable := range envList { matchingRuleFoundForSomeContainer := false @@ -417,7 +525,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceEnvironmentVariablePolicy(conta } func (pe *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID string, workingDir string) error { - possibleIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + possibleIndices := pe.possibleIndicesForID(containerID) matched := false for _, pIndex := range possibleIndices { @@ -429,7 +537,7 @@ func (pe *StandardSecurityPolicyEnforcer) enforceWorkingDirPolicy(containerID st } } if !matched { - return fmt.Errorf("working_dir %s unmatched by policy rule", workingDir) + return fmt.Errorf("working_dir %q unmatched by policy rule", workingDir) } return nil } @@ -453,6 +561,7 @@ func envIsMatchedByRule(envVariable string, rules []EnvRuleConfig) bool { return false } +// StandardSecurityPolicyEnforcer.mutex lock must be held prior to calling this function. func (pe *StandardSecurityPolicyEnforcer) expandMatchesForContainerIndex(index int, idToAdd string) { _, keyExists := pe.ContainerIndexToContainerIds[index] if !keyExists { @@ -462,12 +571,13 @@ func (pe *StandardSecurityPolicyEnforcer) expandMatchesForContainerIndex(index i pe.ContainerIndexToContainerIds[index][idToAdd] = struct{}{} } +// StandardSecurityPolicyEnforcer.mutex lock must be held prior to calling this function. func (pe *StandardSecurityPolicyEnforcer) narrowMatchesForContainerIndex(index int, idToRemove string) { delete(pe.ContainerIndexToContainerIds[index], idToRemove) } func equalForOverlay(a1 []string, a2 []string) bool { - // We've stored the layers from bottom to topl they are in layerPaths as + // We've stored the layers from bottom to top they are in layerPaths as // top to bottom (the order a string gets concatenated for the unix mount // command). W do our check with that in mind. if len(a1) == len(a2) { @@ -483,17 +593,135 @@ func equalForOverlay(a1 []string, a2 []string) bool { return true } -func possibleIndicesForID(containerID string, mapping map[int]map[string]struct{}) []int { - var possibles []int - for index, ids := range mapping { +// StandardSecurityPolicyEnforcer.mutex lock must be held prior to calling this function. +func (pe *StandardSecurityPolicyEnforcer) possibleIndicesForID(containerID string) []int { + var possibleIndices []int + for index, ids := range pe.ContainerIndexToContainerIds { for id := range ids { if containerID == id { - possibles = append(possibles, index) + possibleIndices = append(possibleIndices, index) } } } + return possibleIndices +} - return possibles +func (pe *StandardSecurityPolicyEnforcer) enforceDefaultMounts(specMount oci.Mount) error { + for _, mountConstraint := range pe.DefaultMounts { + if err := mountConstraint.validate(specMount); err == nil { + return nil + } + } + return fmt.Errorf("mount not allowed by default mount constraints: %+v", specMount) +} + +func (pe *StandardSecurityPolicyEnforcer) ExtendDefaultMounts(defaultMounts []oci.Mount) error { + for _, mnt := range defaultMounts { + pe.DefaultMounts = append(pe.DefaultMounts, newMountConstraint( + mnt.Source, + mnt.Destination, + mnt.Type, + mnt.Options, + )) + } + return nil +} + +// EnforceMountPolicy for StandardSecurityPolicyEnforcer validates various +// default mounts injected into container spec by GCS or containerD +func (pe *StandardSecurityPolicyEnforcer) EnforceMountPolicy(sandboxID, containerID string, spec *oci.Spec) (err error) { + pe.mutex.Lock() + defer pe.mutex.Unlock() + + possibleIndices := pe.possibleIndicesForID(containerID) + + for _, specMnt := range spec.Mounts { + // first check against default mounts + if err := pe.enforceDefaultMounts(specMnt); err == nil { + continue + } + + mountOk := false + // check against user provided mount constraints, which helps to figure + // out which container this mount spec corresponds to. + for _, pIndex := range possibleIndices { + cont := pe.Containers[pIndex] + if err = cont.matchMount(sandboxID, specMnt); err == nil { + mountOk = true + } else { + pe.narrowMatchesForContainerIndex(pIndex, containerID) + } + } + + if !mountOk { + retErr := fmt.Errorf("mount %+v is not allowed by mount constraints", specMnt) + return retErr + } + } + return nil +} + +// validate checks given OCI mount against mount policy. Destination is checked +// by direct string comparisons and Source is checked via a regular expression. +// This is done this way, because container path (Destination) is always fixed, +// however, the host/UVM path (Source) can include IDs generated at runtime and +// impossible to know in advance. +// +// NOTE: Different matching strategies can be added by introducing a separate +// path matching config, which isn't needed at the moment. +func (m *mountInternal) validate(mSpec oci.Mount) error { + if m.Type != mSpec.Type { + return fmt.Errorf("mount type not allowed by policy: expected=%q, actual=%q", m.Type, mSpec.Type) + } + if ok, _ := regexp.MatchString(m.Source, mSpec.Source); !ok { + return fmt.Errorf("mount source not allowed by policy: expected=%q, actual=%q", m.Source, mSpec.Source) + } + if m.Destination != mSpec.Destination && m.Destination != "" { + return fmt.Errorf("mount destination not allowed by policy: expected=%q, actual=%q", m.Destination, mSpec.Destination) + } + if !stringSlicesEqual(m.Options, mSpec.Options) { + return fmt.Errorf("mount options not allowed by policy: expected=%q, actual=%q", m.Options, mSpec.Options) + } + return nil +} + +// matchMount matches given OCI mount against mount constraints. If no match +// found, the mount is not allowed. +func (c *securityPolicyContainer) matchMount(sandboxID string, m oci.Mount) (err error) { + for _, constraint := range c.Mounts { + // now that we know the sandboxID we can get the actual path for + // various destination path types by adding a UVM mount prefix + constraint = substituteUVMPath(sandboxID, constraint) + if err = constraint.validate(m); err == nil { + return nil + } + } + return fmt.Errorf("mount is not allowed by policy: %+v", m) +} + +// substituteUVMPath substitutes mount prefix to an appropriate path inside +// UVM. At policy generation time, it's impossible to tell what the sandboxID +// will be, so the prefix substitution needs to happen during runtime. +func substituteUVMPath(sandboxID string, m mountInternal) mountInternal { + if strings.HasPrefix(m.Source, guestpath.SandboxMountPrefix) { + m.Source = specInternal.SandboxMountSource(sandboxID, m.Source) + } else if strings.HasPrefix(m.Source, guestpath.HugePagesMountPrefix) { + m.Source = specInternal.HugePagesMountSource(sandboxID, m.Source) + } + return m +} + +func stringSlicesEqual(slice1, slice2 []string) bool { + if len(slice1) != len(slice2) { + return false + } + + for i := 0; i < len(slice1); i++ { + if slice1[i] != slice2[i] { + return false + } + } + return true } // EnforceExpectedMountsPolicy for StandardSecurityPolicyEnforcer injects a @@ -531,7 +759,7 @@ func (pe *StandardSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(containerI } var wMounts []string - pIndices := possibleIndicesForID(containerID, pe.ContainerIndexToContainerIds) + pIndices := pe.possibleIndicesForID(containerID) if len(pIndices) == 0 { return errors.New("no valid container indices found") } @@ -602,10 +830,18 @@ func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, return nil } +func (OpenDoorSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spec) error { + return nil +} + func (p *OpenDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return nil } +func (OpenDoorSecurityPolicyEnforcer) ExtendDefaultMounts(_ []oci.Mount) error { + return nil +} + type ClosedDoorSecurityPolicyEnforcer struct{} var _ SecurityPolicyEnforcer = (*ClosedDoorSecurityPolicyEnforcer)(nil) @@ -629,3 +865,11 @@ func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string func (p *ClosedDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return errors.New("enforcing expected mounts is denied by policy") } + +func (ClosedDoorSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spec) error { + return errors.New("container mounts are denied by policy") +} + +func (ClosedDoorSecurityPolicyEnforcer) ExtendDefaultMounts(_ []oci.Mount) error { + return nil +} diff --git a/test/vendor/github.com/google/go-cmp/LICENSE b/test/vendor/github.com/google/go-cmp/LICENSE deleted file mode 100644 index 32017f8fa1..0000000000 --- a/test/vendor/github.com/google/go-cmp/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2017 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/test/vendor/github.com/google/go-cmp/cmp/compare.go b/test/vendor/github.com/google/go-cmp/cmp/compare.go deleted file mode 100644 index 86d0903b8b..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/compare.go +++ /dev/null @@ -1,682 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package cmp determines equality of values. -// -// This package is intended to be a more powerful and safer alternative to -// reflect.DeepEqual for comparing whether two values are semantically equal. -// It is intended to only be used in tests, as performance is not a goal and -// it may panic if it cannot compare the values. Its propensity towards -// panicking means that its unsuitable for production environments where a -// spurious panic may be fatal. -// -// The primary features of cmp are: -// -// • When the default behavior of equality does not suit the needs of the test, -// custom equality functions can override the equality operation. -// For example, an equality function may report floats as equal so long as they -// are within some tolerance of each other. -// -// • Types that have an Equal method may use that method to determine equality. -// This allows package authors to determine the equality operation for the types -// that they define. -// -// • If no custom equality functions are used and no Equal method is defined, -// equality is determined by recursively comparing the primitive kinds on both -// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported -// fields are not compared by default; they result in panics unless suppressed -// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly -// compared using the Exporter option. -package cmp - -import ( - "fmt" - "reflect" - "strings" - - "github.com/google/go-cmp/cmp/internal/diff" - "github.com/google/go-cmp/cmp/internal/flags" - "github.com/google/go-cmp/cmp/internal/function" - "github.com/google/go-cmp/cmp/internal/value" -) - -// Equal reports whether x and y are equal by recursively applying the -// following rules in the given order to x and y and all of their sub-values: -// -// • Let S be the set of all Ignore, Transformer, and Comparer options that -// remain after applying all path filters, value filters, and type filters. -// If at least one Ignore exists in S, then the comparison is ignored. -// If the number of Transformer and Comparer options in S is greater than one, -// then Equal panics because it is ambiguous which option to use. -// If S contains a single Transformer, then use that to transform the current -// values and recursively call Equal on the output values. -// If S contains a single Comparer, then use that to compare the current values. -// Otherwise, evaluation proceeds to the next rule. -// -// • If the values have an Equal method of the form "(T) Equal(T) bool" or -// "(T) Equal(I) bool" where T is assignable to I, then use the result of -// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and -// evaluation proceeds to the next rule. -// -// • Lastly, try to compare x and y based on their basic kinds. -// Simple kinds like booleans, integers, floats, complex numbers, strings, and -// channels are compared using the equivalent of the == operator in Go. -// Functions are only equal if they are both nil, otherwise they are unequal. -// -// Structs are equal if recursively calling Equal on all fields report equal. -// If a struct contains unexported fields, Equal panics unless an Ignore option -// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option -// explicitly permits comparing the unexported field. -// -// Slices are equal if they are both nil or both non-nil, where recursively -// calling Equal on all non-ignored slice or array elements report equal. -// Empty non-nil slices and nil slices are not equal; to equate empty slices, -// consider using cmpopts.EquateEmpty. -// -// Maps are equal if they are both nil or both non-nil, where recursively -// calling Equal on all non-ignored map entries report equal. -// Map keys are equal according to the == operator. -// To use custom comparisons for map keys, consider using cmpopts.SortMaps. -// Empty non-nil maps and nil maps are not equal; to equate empty maps, -// consider using cmpopts.EquateEmpty. -// -// Pointers and interfaces are equal if they are both nil or both non-nil, -// where they have the same underlying concrete type and recursively -// calling Equal on the underlying values reports equal. -// -// Before recursing into a pointer, slice element, or map, the current path -// is checked to detect whether the address has already been visited. -// If there is a cycle, then the pointed at values are considered equal -// only if both addresses were previously visited in the same path step. -func Equal(x, y interface{}, opts ...Option) bool { - s := newState(opts) - s.compareAny(rootStep(x, y)) - return s.result.Equal() -} - -// Diff returns a human-readable report of the differences between two values: -// y - x. It returns an empty string if and only if Equal returns true for the -// same input values and options. -// -// The output is displayed as a literal in pseudo-Go syntax. -// At the start of each line, a "-" prefix indicates an element removed from x, -// a "+" prefix to indicates an element added from y, and the lack of a prefix -// indicates an element common to both x and y. If possible, the output -// uses fmt.Stringer.String or error.Error methods to produce more humanly -// readable outputs. In such cases, the string is prefixed with either an -// 's' or 'e' character, respectively, to indicate that the method was called. -// -// Do not depend on this output being stable. If you need the ability to -// programmatically interpret the difference, consider using a custom Reporter. -func Diff(x, y interface{}, opts ...Option) string { - s := newState(opts) - - // Optimization: If there are no other reporters, we can optimize for the - // common case where the result is equal (and thus no reported difference). - // This avoids the expensive construction of a difference tree. - if len(s.reporters) == 0 { - s.compareAny(rootStep(x, y)) - if s.result.Equal() { - return "" - } - s.result = diff.Result{} // Reset results - } - - r := new(defaultReporter) - s.reporters = append(s.reporters, reporter{r}) - s.compareAny(rootStep(x, y)) - d := r.String() - if (d == "") != s.result.Equal() { - panic("inconsistent difference and equality results") - } - return d -} - -// rootStep constructs the first path step. If x and y have differing types, -// then they are stored within an empty interface type. -func rootStep(x, y interface{}) PathStep { - vx := reflect.ValueOf(x) - vy := reflect.ValueOf(y) - - // If the inputs are different types, auto-wrap them in an empty interface - // so that they have the same parent type. - var t reflect.Type - if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { - t = reflect.TypeOf((*interface{})(nil)).Elem() - if vx.IsValid() { - vvx := reflect.New(t).Elem() - vvx.Set(vx) - vx = vvx - } - if vy.IsValid() { - vvy := reflect.New(t).Elem() - vvy.Set(vy) - vy = vvy - } - } else { - t = vx.Type() - } - - return &pathStep{t, vx, vy} -} - -type state struct { - // These fields represent the "comparison state". - // Calling statelessCompare must not result in observable changes to these. - result diff.Result // The current result of comparison - curPath Path // The current path in the value tree - curPtrs pointerPath // The current set of visited pointers - reporters []reporter // Optional reporters - - // recChecker checks for infinite cycles applying the same set of - // transformers upon the output of itself. - recChecker recChecker - - // dynChecker triggers pseudo-random checks for option correctness. - // It is safe for statelessCompare to mutate this value. - dynChecker dynChecker - - // These fields, once set by processOption, will not change. - exporters []exporter // List of exporters for structs with unexported fields - opts Options // List of all fundamental and filter options -} - -func newState(opts []Option) *state { - // Always ensure a validator option exists to validate the inputs. - s := &state{opts: Options{validator{}}} - s.curPtrs.Init() - s.processOption(Options(opts)) - return s -} - -func (s *state) processOption(opt Option) { - switch opt := opt.(type) { - case nil: - case Options: - for _, o := range opt { - s.processOption(o) - } - case coreOption: - type filtered interface { - isFiltered() bool - } - if fopt, ok := opt.(filtered); ok && !fopt.isFiltered() { - panic(fmt.Sprintf("cannot use an unfiltered option: %v", opt)) - } - s.opts = append(s.opts, opt) - case exporter: - s.exporters = append(s.exporters, opt) - case reporter: - s.reporters = append(s.reporters, opt) - default: - panic(fmt.Sprintf("unknown option %T", opt)) - } -} - -// statelessCompare compares two values and returns the result. -// This function is stateless in that it does not alter the current result, -// or output to any registered reporters. -func (s *state) statelessCompare(step PathStep) diff.Result { - // We do not save and restore curPath and curPtrs because all of the - // compareX methods should properly push and pop from them. - // It is an implementation bug if the contents of the paths differ from - // when calling this function to when returning from it. - - oldResult, oldReporters := s.result, s.reporters - s.result = diff.Result{} // Reset result - s.reporters = nil // Remove reporters to avoid spurious printouts - s.compareAny(step) - res := s.result - s.result, s.reporters = oldResult, oldReporters - return res -} - -func (s *state) compareAny(step PathStep) { - // Update the path stack. - s.curPath.push(step) - defer s.curPath.pop() - for _, r := range s.reporters { - r.PushStep(step) - defer r.PopStep() - } - s.recChecker.Check(s.curPath) - - // Cycle-detection for slice elements (see NOTE in compareSlice). - t := step.Type() - vx, vy := step.Values() - if si, ok := step.(SliceIndex); ok && si.isSlice && vx.IsValid() && vy.IsValid() { - px, py := vx.Addr(), vy.Addr() - if eq, visited := s.curPtrs.Push(px, py); visited { - s.report(eq, reportByCycle) - return - } - defer s.curPtrs.Pop(px, py) - } - - // Rule 1: Check whether an option applies on this node in the value tree. - if s.tryOptions(t, vx, vy) { - return - } - - // Rule 2: Check whether the type has a valid Equal method. - if s.tryMethod(t, vx, vy) { - return - } - - // Rule 3: Compare based on the underlying kind. - switch t.Kind() { - case reflect.Bool: - s.report(vx.Bool() == vy.Bool(), 0) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - s.report(vx.Int() == vy.Int(), 0) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - s.report(vx.Uint() == vy.Uint(), 0) - case reflect.Float32, reflect.Float64: - s.report(vx.Float() == vy.Float(), 0) - case reflect.Complex64, reflect.Complex128: - s.report(vx.Complex() == vy.Complex(), 0) - case reflect.String: - s.report(vx.String() == vy.String(), 0) - case reflect.Chan, reflect.UnsafePointer: - s.report(vx.Pointer() == vy.Pointer(), 0) - case reflect.Func: - s.report(vx.IsNil() && vy.IsNil(), 0) - case reflect.Struct: - s.compareStruct(t, vx, vy) - case reflect.Slice, reflect.Array: - s.compareSlice(t, vx, vy) - case reflect.Map: - s.compareMap(t, vx, vy) - case reflect.Ptr: - s.comparePtr(t, vx, vy) - case reflect.Interface: - s.compareInterface(t, vx, vy) - default: - panic(fmt.Sprintf("%v kind not handled", t.Kind())) - } -} - -func (s *state) tryOptions(t reflect.Type, vx, vy reflect.Value) bool { - // Evaluate all filters and apply the remaining options. - if opt := s.opts.filter(s, t, vx, vy); opt != nil { - opt.apply(s, vx, vy) - return true - } - return false -} - -func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool { - // Check if this type even has an Equal method. - m, ok := t.MethodByName("Equal") - if !ok || !function.IsType(m.Type, function.EqualAssignable) { - return false - } - - eq := s.callTTBFunc(m.Func, vx, vy) - s.report(eq, reportByMethod) - return true -} - -func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { - v = sanitizeValue(v, f.Type().In(0)) - if !s.dynChecker.Next() { - return f.Call([]reflect.Value{v})[0] - } - - // Run the function twice and ensure that we get the same results back. - // We run in goroutines so that the race detector (if enabled) can detect - // unsafe mutations to the input. - c := make(chan reflect.Value) - go detectRaces(c, f, v) - got := <-c - want := f.Call([]reflect.Value{v})[0] - if step.vx, step.vy = got, want; !s.statelessCompare(step).Equal() { - // To avoid false-positives with non-reflexive equality operations, - // we sanity check whether a value is equal to itself. - if step.vx, step.vy = want, want; !s.statelessCompare(step).Equal() { - return want - } - panic(fmt.Sprintf("non-deterministic function detected: %s", function.NameOf(f))) - } - return want -} - -func (s *state) callTTBFunc(f, x, y reflect.Value) bool { - x = sanitizeValue(x, f.Type().In(0)) - y = sanitizeValue(y, f.Type().In(1)) - if !s.dynChecker.Next() { - return f.Call([]reflect.Value{x, y})[0].Bool() - } - - // Swapping the input arguments is sufficient to check that - // f is symmetric and deterministic. - // We run in goroutines so that the race detector (if enabled) can detect - // unsafe mutations to the input. - c := make(chan reflect.Value) - go detectRaces(c, f, y, x) - got := <-c - want := f.Call([]reflect.Value{x, y})[0].Bool() - if !got.IsValid() || got.Bool() != want { - panic(fmt.Sprintf("non-deterministic or non-symmetric function detected: %s", function.NameOf(f))) - } - return want -} - -func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) { - var ret reflect.Value - defer func() { - recover() // Ignore panics, let the other call to f panic instead - c <- ret - }() - ret = f.Call(vs)[0] -} - -// sanitizeValue converts nil interfaces of type T to those of type R, -// assuming that T is assignable to R. -// Otherwise, it returns the input value as is. -func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value { - // TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143). - if !flags.AtLeastGo110 { - if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { - return reflect.New(t).Elem() - } - } - return v -} - -func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { - var addr bool - var vax, vay reflect.Value // Addressable versions of vx and vy - - var mayForce, mayForceInit bool - step := StructField{&structField{}} - for i := 0; i < t.NumField(); i++ { - step.typ = t.Field(i).Type - step.vx = vx.Field(i) - step.vy = vy.Field(i) - step.name = t.Field(i).Name - step.idx = i - step.unexported = !isExported(step.name) - if step.unexported { - if step.name == "_" { - continue - } - // Defer checking of unexported fields until later to give an - // Ignore a chance to ignore the field. - if !vax.IsValid() || !vay.IsValid() { - // For retrieveUnexportedField to work, the parent struct must - // be addressable. Create a new copy of the values if - // necessary to make them addressable. - addr = vx.CanAddr() || vy.CanAddr() - vax = makeAddressable(vx) - vay = makeAddressable(vy) - } - if !mayForceInit { - for _, xf := range s.exporters { - mayForce = mayForce || xf(t) - } - mayForceInit = true - } - step.mayForce = mayForce - step.paddr = addr - step.pvx = vax - step.pvy = vay - step.field = t.Field(i) - } - s.compareAny(step) - } -} - -func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) { - isSlice := t.Kind() == reflect.Slice - if isSlice && (vx.IsNil() || vy.IsNil()) { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - - // NOTE: It is incorrect to call curPtrs.Push on the slice header pointer - // since slices represents a list of pointers, rather than a single pointer. - // The pointer checking logic must be handled on a per-element basis - // in compareAny. - // - // A slice header (see reflect.SliceHeader) in Go is a tuple of a starting - // pointer P, a length N, and a capacity C. Supposing each slice element has - // a memory size of M, then the slice is equivalent to the list of pointers: - // [P+i*M for i in range(N)] - // - // For example, v[:0] and v[:1] are slices with the same starting pointer, - // but they are clearly different values. Using the slice pointer alone - // violates the assumption that equal pointers implies equal values. - - step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}, isSlice: isSlice}} - withIndexes := func(ix, iy int) SliceIndex { - if ix >= 0 { - step.vx, step.xkey = vx.Index(ix), ix - } else { - step.vx, step.xkey = reflect.Value{}, -1 - } - if iy >= 0 { - step.vy, step.ykey = vy.Index(iy), iy - } else { - step.vy, step.ykey = reflect.Value{}, -1 - } - return step - } - - // Ignore options are able to ignore missing elements in a slice. - // However, detecting these reliably requires an optimal differencing - // algorithm, for which diff.Difference is not. - // - // Instead, we first iterate through both slices to detect which elements - // would be ignored if standing alone. The index of non-discarded elements - // are stored in a separate slice, which diffing is then performed on. - var indexesX, indexesY []int - var ignoredX, ignoredY []bool - for ix := 0; ix < vx.Len(); ix++ { - ignored := s.statelessCompare(withIndexes(ix, -1)).NumDiff == 0 - if !ignored { - indexesX = append(indexesX, ix) - } - ignoredX = append(ignoredX, ignored) - } - for iy := 0; iy < vy.Len(); iy++ { - ignored := s.statelessCompare(withIndexes(-1, iy)).NumDiff == 0 - if !ignored { - indexesY = append(indexesY, iy) - } - ignoredY = append(ignoredY, ignored) - } - - // Compute an edit-script for slices vx and vy (excluding ignored elements). - edits := diff.Difference(len(indexesX), len(indexesY), func(ix, iy int) diff.Result { - return s.statelessCompare(withIndexes(indexesX[ix], indexesY[iy])) - }) - - // Replay the ignore-scripts and the edit-script. - var ix, iy int - for ix < vx.Len() || iy < vy.Len() { - var e diff.EditType - switch { - case ix < len(ignoredX) && ignoredX[ix]: - e = diff.UniqueX - case iy < len(ignoredY) && ignoredY[iy]: - e = diff.UniqueY - default: - e, edits = edits[0], edits[1:] - } - switch e { - case diff.UniqueX: - s.compareAny(withIndexes(ix, -1)) - ix++ - case diff.UniqueY: - s.compareAny(withIndexes(-1, iy)) - iy++ - default: - s.compareAny(withIndexes(ix, iy)) - ix++ - iy++ - } - } -} - -func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - - // Cycle-detection for maps. - if eq, visited := s.curPtrs.Push(vx, vy); visited { - s.report(eq, reportByCycle) - return - } - defer s.curPtrs.Pop(vx, vy) - - // We combine and sort the two map keys so that we can perform the - // comparisons in a deterministic order. - step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}} - for _, k := range value.SortKeys(append(vx.MapKeys(), vy.MapKeys()...)) { - step.vx = vx.MapIndex(k) - step.vy = vy.MapIndex(k) - step.key = k - if !step.vx.IsValid() && !step.vy.IsValid() { - // It is possible for both vx and vy to be invalid if the - // key contained a NaN value in it. - // - // Even with the ability to retrieve NaN keys in Go 1.12, - // there still isn't a sensible way to compare the values since - // a NaN key may map to multiple unordered values. - // The most reasonable way to compare NaNs would be to compare the - // set of values. However, this is impossible to do efficiently - // since set equality is provably an O(n^2) operation given only - // an Equal function. If we had a Less function or Hash function, - // this could be done in O(n*log(n)) or O(n), respectively. - // - // Rather than adding complex logic to deal with NaNs, make it - // the user's responsibility to compare such obscure maps. - const help = "consider providing a Comparer to compare the map" - panic(fmt.Sprintf("%#v has map key with NaNs\n%s", s.curPath, help)) - } - s.compareAny(step) - } -} - -func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) { - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - - // Cycle-detection for pointers. - if eq, visited := s.curPtrs.Push(vx, vy); visited { - s.report(eq, reportByCycle) - return - } - defer s.curPtrs.Pop(vx, vy) - - vx, vy = vx.Elem(), vy.Elem() - s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}}) -} - -func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) { - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - vx, vy = vx.Elem(), vy.Elem() - if vx.Type() != vy.Type() { - s.report(false, 0) - return - } - s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}}) -} - -func (s *state) report(eq bool, rf resultFlags) { - if rf&reportByIgnore == 0 { - if eq { - s.result.NumSame++ - rf |= reportEqual - } else { - s.result.NumDiff++ - rf |= reportUnequal - } - } - for _, r := range s.reporters { - r.Report(Result{flags: rf}) - } -} - -// recChecker tracks the state needed to periodically perform checks that -// user provided transformers are not stuck in an infinitely recursive cycle. -type recChecker struct{ next int } - -// Check scans the Path for any recursive transformers and panics when any -// recursive transformers are detected. Note that the presence of a -// recursive Transformer does not necessarily imply an infinite cycle. -// As such, this check only activates after some minimal number of path steps. -func (rc *recChecker) Check(p Path) { - const minLen = 1 << 16 - if rc.next == 0 { - rc.next = minLen - } - if len(p) < rc.next { - return - } - rc.next <<= 1 - - // Check whether the same transformer has appeared at least twice. - var ss []string - m := map[Option]int{} - for _, ps := range p { - if t, ok := ps.(Transform); ok { - t := t.Option() - if m[t] == 1 { // Transformer was used exactly once before - tf := t.(*transformer).fnc.Type() - ss = append(ss, fmt.Sprintf("%v: %v => %v", t, tf.In(0), tf.Out(0))) - } - m[t]++ - } - } - if len(ss) > 0 { - const warning = "recursive set of Transformers detected" - const help = "consider using cmpopts.AcyclicTransformer" - set := strings.Join(ss, "\n\t") - panic(fmt.Sprintf("%s:\n\t%s\n%s", warning, set, help)) - } -} - -// dynChecker tracks the state needed to periodically perform checks that -// user provided functions are symmetric and deterministic. -// The zero value is safe for immediate use. -type dynChecker struct{ curr, next int } - -// Next increments the state and reports whether a check should be performed. -// -// Checks occur every Nth function call, where N is a triangular number: -// 0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ... -// See https://en.wikipedia.org/wiki/Triangular_number -// -// This sequence ensures that the cost of checks drops significantly as -// the number of functions calls grows larger. -func (dc *dynChecker) Next() bool { - ok := dc.curr == dc.next - if ok { - dc.curr = 0 - dc.next++ - } - dc.curr++ - return ok -} - -// makeAddressable returns a value that is always addressable. -// It returns the input verbatim if it is already addressable, -// otherwise it creates a new value and returns an addressable copy. -func makeAddressable(v reflect.Value) reflect.Value { - if v.CanAddr() { - return v - } - vc := reflect.New(v.Type()).Elem() - vc.Set(v) - return vc -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/export_panic.go b/test/vendor/github.com/google/go-cmp/cmp/export_panic.go deleted file mode 100644 index 5ff0b4218c..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/export_panic.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build purego - -package cmp - -import "reflect" - -const supportExporters = false - -func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value { - panic("no support for forcibly accessing unexported fields") -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/export_unsafe.go b/test/vendor/github.com/google/go-cmp/cmp/export_unsafe.go deleted file mode 100644 index 21eb54858e..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/export_unsafe.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !purego - -package cmp - -import ( - "reflect" - "unsafe" -) - -const supportExporters = true - -// retrieveUnexportedField uses unsafe to forcibly retrieve any field from -// a struct such that the value has read-write permissions. -// -// The parent struct, v, must be addressable, while f must be a StructField -// describing the field to retrieve. If addr is false, -// then the returned value will be shallowed copied to be non-addressable. -func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value { - ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem() - if !addr { - // A field is addressable if and only if the struct is addressable. - // If the original parent value was not addressable, shallow copy the - // value to make it non-addressable to avoid leaking an implementation - // detail of how forcibly exporting a field works. - if ve.Kind() == reflect.Interface && ve.IsNil() { - return reflect.Zero(f.Type) - } - return reflect.ValueOf(ve.Interface()).Convert(f.Type) - } - return ve -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go b/test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go deleted file mode 100644 index 1daaaacc5e..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !cmp_debug - -package diff - -var debug debugger - -type debugger struct{} - -func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc { - return f -} -func (debugger) Update() {} -func (debugger) Finish() {} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go b/test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go deleted file mode 100644 index 4b91dbcaca..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build cmp_debug - -package diff - -import ( - "fmt" - "strings" - "sync" - "time" -) - -// The algorithm can be seen running in real-time by enabling debugging: -// go test -tags=cmp_debug -v -// -// Example output: -// === RUN TestDifference/#34 -// ┌───────────────────────────────┐ -// │ \ · · · · · · · · · · · · · · │ -// │ · # · · · · · · · · · · · · · │ -// │ · \ · · · · · · · · · · · · · │ -// │ · · \ · · · · · · · · · · · · │ -// │ · · · X # · · · · · · · · · · │ -// │ · · · # \ · · · · · · · · · · │ -// │ · · · · · # # · · · · · · · · │ -// │ · · · · · # \ · · · · · · · · │ -// │ · · · · · · · \ · · · · · · · │ -// │ · · · · · · · · \ · · · · · · │ -// │ · · · · · · · · · \ · · · · · │ -// │ · · · · · · · · · · \ · · # · │ -// │ · · · · · · · · · · · \ # # · │ -// │ · · · · · · · · · · · # # # · │ -// │ · · · · · · · · · · # # # # · │ -// │ · · · · · · · · · # # # # # · │ -// │ · · · · · · · · · · · · · · \ │ -// └───────────────────────────────┘ -// [.Y..M.XY......YXYXY.|] -// -// The grid represents the edit-graph where the horizontal axis represents -// list X and the vertical axis represents list Y. The start of the two lists -// is the top-left, while the ends are the bottom-right. The '·' represents -// an unexplored node in the graph. The '\' indicates that the two symbols -// from list X and Y are equal. The 'X' indicates that two symbols are similar -// (but not exactly equal) to each other. The '#' indicates that the two symbols -// are different (and not similar). The algorithm traverses this graph trying to -// make the paths starting in the top-left and the bottom-right connect. -// -// The series of '.', 'X', 'Y', and 'M' characters at the bottom represents -// the currently established path from the forward and reverse searches, -// separated by a '|' character. - -const ( - updateDelay = 100 * time.Millisecond - finishDelay = 500 * time.Millisecond - ansiTerminal = true // ANSI escape codes used to move terminal cursor -) - -var debug debugger - -type debugger struct { - sync.Mutex - p1, p2 EditScript - fwdPath, revPath *EditScript - grid []byte - lines int -} - -func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc { - dbg.Lock() - dbg.fwdPath, dbg.revPath = p1, p2 - top := "┌─" + strings.Repeat("──", nx) + "┐\n" - row := "│ " + strings.Repeat("· ", nx) + "│\n" - btm := "└─" + strings.Repeat("──", nx) + "┘\n" - dbg.grid = []byte(top + strings.Repeat(row, ny) + btm) - dbg.lines = strings.Count(dbg.String(), "\n") - fmt.Print(dbg) - - // Wrap the EqualFunc so that we can intercept each result. - return func(ix, iy int) (r Result) { - cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")] - for i := range cell { - cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot - } - switch r = f(ix, iy); { - case r.Equal(): - cell[0] = '\\' - case r.Similar(): - cell[0] = 'X' - default: - cell[0] = '#' - } - return - } -} - -func (dbg *debugger) Update() { - dbg.print(updateDelay) -} - -func (dbg *debugger) Finish() { - dbg.print(finishDelay) - dbg.Unlock() -} - -func (dbg *debugger) String() string { - dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0] - for i := len(*dbg.revPath) - 1; i >= 0; i-- { - dbg.p2 = append(dbg.p2, (*dbg.revPath)[i]) - } - return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2) -} - -func (dbg *debugger) print(d time.Duration) { - if ansiTerminal { - fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor - } - fmt.Print(dbg) - time.Sleep(d) -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go b/test/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go deleted file mode 100644 index bc196b16cf..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package diff implements an algorithm for producing edit-scripts. -// The edit-script is a sequence of operations needed to transform one list -// of symbols into another (or vice-versa). The edits allowed are insertions, -// deletions, and modifications. The summation of all edits is called the -// Levenshtein distance as this problem is well-known in computer science. -// -// This package prioritizes performance over accuracy. That is, the run time -// is more important than obtaining a minimal Levenshtein distance. -package diff - -import ( - "math/rand" - "time" - - "github.com/google/go-cmp/cmp/internal/flags" -) - -// EditType represents a single operation within an edit-script. -type EditType uint8 - -const ( - // Identity indicates that a symbol pair is identical in both list X and Y. - Identity EditType = iota - // UniqueX indicates that a symbol only exists in X and not Y. - UniqueX - // UniqueY indicates that a symbol only exists in Y and not X. - UniqueY - // Modified indicates that a symbol pair is a modification of each other. - Modified -) - -// EditScript represents the series of differences between two lists. -type EditScript []EditType - -// String returns a human-readable string representing the edit-script where -// Identity, UniqueX, UniqueY, and Modified are represented by the -// '.', 'X', 'Y', and 'M' characters, respectively. -func (es EditScript) String() string { - b := make([]byte, len(es)) - for i, e := range es { - switch e { - case Identity: - b[i] = '.' - case UniqueX: - b[i] = 'X' - case UniqueY: - b[i] = 'Y' - case Modified: - b[i] = 'M' - default: - panic("invalid edit-type") - } - } - return string(b) -} - -// stats returns a histogram of the number of each type of edit operation. -func (es EditScript) stats() (s struct{ NI, NX, NY, NM int }) { - for _, e := range es { - switch e { - case Identity: - s.NI++ - case UniqueX: - s.NX++ - case UniqueY: - s.NY++ - case Modified: - s.NM++ - default: - panic("invalid edit-type") - } - } - return -} - -// Dist is the Levenshtein distance and is guaranteed to be 0 if and only if -// lists X and Y are equal. -func (es EditScript) Dist() int { return len(es) - es.stats().NI } - -// LenX is the length of the X list. -func (es EditScript) LenX() int { return len(es) - es.stats().NY } - -// LenY is the length of the Y list. -func (es EditScript) LenY() int { return len(es) - es.stats().NX } - -// EqualFunc reports whether the symbols at indexes ix and iy are equal. -// When called by Difference, the index is guaranteed to be within nx and ny. -type EqualFunc func(ix int, iy int) Result - -// Result is the result of comparison. -// NumSame is the number of sub-elements that are equal. -// NumDiff is the number of sub-elements that are not equal. -type Result struct{ NumSame, NumDiff int } - -// BoolResult returns a Result that is either Equal or not Equal. -func BoolResult(b bool) Result { - if b { - return Result{NumSame: 1} // Equal, Similar - } else { - return Result{NumDiff: 2} // Not Equal, not Similar - } -} - -// Equal indicates whether the symbols are equal. Two symbols are equal -// if and only if NumDiff == 0. If Equal, then they are also Similar. -func (r Result) Equal() bool { return r.NumDiff == 0 } - -// Similar indicates whether two symbols are similar and may be represented -// by using the Modified type. As a special case, we consider binary comparisons -// (i.e., those that return Result{1, 0} or Result{0, 1}) to be similar. -// -// The exact ratio of NumSame to NumDiff to determine similarity may change. -func (r Result) Similar() bool { - // Use NumSame+1 to offset NumSame so that binary comparisons are similar. - return r.NumSame+1 >= r.NumDiff -} - -var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 - -// Difference reports whether two lists of lengths nx and ny are equal -// given the definition of equality provided as f. -// -// This function returns an edit-script, which is a sequence of operations -// needed to convert one list into the other. The following invariants for -// the edit-script are maintained: -// • eq == (es.Dist()==0) -// • nx == es.LenX() -// • ny == es.LenY() -// -// This algorithm is not guaranteed to be an optimal solution (i.e., one that -// produces an edit-script with a minimal Levenshtein distance). This algorithm -// favors performance over optimality. The exact output is not guaranteed to -// be stable and may change over time. -func Difference(nx, ny int, f EqualFunc) (es EditScript) { - // This algorithm is based on traversing what is known as an "edit-graph". - // See Figure 1 from "An O(ND) Difference Algorithm and Its Variations" - // by Eugene W. Myers. Since D can be as large as N itself, this is - // effectively O(N^2). Unlike the algorithm from that paper, we are not - // interested in the optimal path, but at least some "decent" path. - // - // For example, let X and Y be lists of symbols: - // X = [A B C A B B A] - // Y = [C B A B A C] - // - // The edit-graph can be drawn as the following: - // A B C A B B A - // ┌─────────────┐ - // C │_|_|\|_|_|_|_│ 0 - // B │_|\|_|_|\|\|_│ 1 - // A │\|_|_|\|_|_|\│ 2 - // B │_|\|_|_|\|\|_│ 3 - // A │\|_|_|\|_|_|\│ 4 - // C │ | |\| | | | │ 5 - // └─────────────┘ 6 - // 0 1 2 3 4 5 6 7 - // - // List X is written along the horizontal axis, while list Y is written - // along the vertical axis. At any point on this grid, if the symbol in - // list X matches the corresponding symbol in list Y, then a '\' is drawn. - // The goal of any minimal edit-script algorithm is to find a path from the - // top-left corner to the bottom-right corner, while traveling through the - // fewest horizontal or vertical edges. - // A horizontal edge is equivalent to inserting a symbol from list X. - // A vertical edge is equivalent to inserting a symbol from list Y. - // A diagonal edge is equivalent to a matching symbol between both X and Y. - - // Invariants: - // • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx - // • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny - // - // In general: - // • fwdFrontier.X < revFrontier.X - // • fwdFrontier.Y < revFrontier.Y - // Unless, it is time for the algorithm to terminate. - fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} - revPath := path{-1, point{nx, ny}, make(EditScript, 0)} - fwdFrontier := fwdPath.point // Forward search frontier - revFrontier := revPath.point // Reverse search frontier - - // Search budget bounds the cost of searching for better paths. - // The longest sequence of non-matching symbols that can be tolerated is - // approximately the square-root of the search budget. - searchBudget := 4 * (nx + ny) // O(n) - - // Running the tests with the "cmp_debug" build tag prints a visualization - // of the algorithm running in real-time. This is educational for - // understanding how the algorithm works. See debug_enable.go. - f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es) - - // The algorithm below is a greedy, meet-in-the-middle algorithm for - // computing sub-optimal edit-scripts between two lists. - // - // The algorithm is approximately as follows: - // • Searching for differences switches back-and-forth between - // a search that starts at the beginning (the top-left corner), and - // a search that starts at the end (the bottom-right corner). The goal of - // the search is connect with the search from the opposite corner. - // • As we search, we build a path in a greedy manner, where the first - // match seen is added to the path (this is sub-optimal, but provides a - // decent result in practice). When matches are found, we try the next pair - // of symbols in the lists and follow all matches as far as possible. - // • When searching for matches, we search along a diagonal going through - // through the "frontier" point. If no matches are found, we advance the - // frontier towards the opposite corner. - // • This algorithm terminates when either the X coordinates or the - // Y coordinates of the forward and reverse frontier points ever intersect. - - // This algorithm is correct even if searching only in the forward direction - // or in the reverse direction. We do both because it is commonly observed - // that two lists commonly differ because elements were added to the front - // or end of the other list. - // - // Non-deterministically start with either the forward or reverse direction - // to introduce some deliberate instability so that we have the flexibility - // to change this algorithm in the future. - if flags.Deterministic || randBool { - goto forwardSearch - } else { - goto reverseSearch - } - -forwardSearch: - { - // Forward search from the beginning. - if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 { - goto finishSearch - } - for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ { - // Search in a diagonal pattern for a match. - z := zigzag(i) - p := point{fwdFrontier.X + z, fwdFrontier.Y - z} - switch { - case p.X >= revPath.X || p.Y < fwdPath.Y: - stop1 = true // Hit top-right corner - case p.Y >= revPath.Y || p.X < fwdPath.X: - stop2 = true // Hit bottom-left corner - case f(p.X, p.Y).Equal(): - // Match found, so connect the path to this point. - fwdPath.connect(p, f) - fwdPath.append(Identity) - // Follow sequence of matches as far as possible. - for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y { - if !f(fwdPath.X, fwdPath.Y).Equal() { - break - } - fwdPath.append(Identity) - } - fwdFrontier = fwdPath.point - stop1, stop2 = true, true - default: - searchBudget-- // Match not found - } - debug.Update() - } - // Advance the frontier towards reverse point. - if revPath.X-fwdFrontier.X >= revPath.Y-fwdFrontier.Y { - fwdFrontier.X++ - } else { - fwdFrontier.Y++ - } - goto reverseSearch - } - -reverseSearch: - { - // Reverse search from the end. - if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 { - goto finishSearch - } - for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ { - // Search in a diagonal pattern for a match. - z := zigzag(i) - p := point{revFrontier.X - z, revFrontier.Y + z} - switch { - case fwdPath.X >= p.X || revPath.Y < p.Y: - stop1 = true // Hit bottom-left corner - case fwdPath.Y >= p.Y || revPath.X < p.X: - stop2 = true // Hit top-right corner - case f(p.X-1, p.Y-1).Equal(): - // Match found, so connect the path to this point. - revPath.connect(p, f) - revPath.append(Identity) - // Follow sequence of matches as far as possible. - for fwdPath.X < revPath.X && fwdPath.Y < revPath.Y { - if !f(revPath.X-1, revPath.Y-1).Equal() { - break - } - revPath.append(Identity) - } - revFrontier = revPath.point - stop1, stop2 = true, true - default: - searchBudget-- // Match not found - } - debug.Update() - } - // Advance the frontier towards forward point. - if revFrontier.X-fwdPath.X >= revFrontier.Y-fwdPath.Y { - revFrontier.X-- - } else { - revFrontier.Y-- - } - goto forwardSearch - } - -finishSearch: - // Join the forward and reverse paths and then append the reverse path. - fwdPath.connect(revPath.point, f) - for i := len(revPath.es) - 1; i >= 0; i-- { - t := revPath.es[i] - revPath.es = revPath.es[:i] - fwdPath.append(t) - } - debug.Finish() - return fwdPath.es -} - -type path struct { - dir int // +1 if forward, -1 if reverse - point // Leading point of the EditScript path - es EditScript -} - -// connect appends any necessary Identity, Modified, UniqueX, or UniqueY types -// to the edit-script to connect p.point to dst. -func (p *path) connect(dst point, f EqualFunc) { - if p.dir > 0 { - // Connect in forward direction. - for dst.X > p.X && dst.Y > p.Y { - switch r := f(p.X, p.Y); { - case r.Equal(): - p.append(Identity) - case r.Similar(): - p.append(Modified) - case dst.X-p.X >= dst.Y-p.Y: - p.append(UniqueX) - default: - p.append(UniqueY) - } - } - for dst.X > p.X { - p.append(UniqueX) - } - for dst.Y > p.Y { - p.append(UniqueY) - } - } else { - // Connect in reverse direction. - for p.X > dst.X && p.Y > dst.Y { - switch r := f(p.X-1, p.Y-1); { - case r.Equal(): - p.append(Identity) - case r.Similar(): - p.append(Modified) - case p.Y-dst.Y >= p.X-dst.X: - p.append(UniqueY) - default: - p.append(UniqueX) - } - } - for p.X > dst.X { - p.append(UniqueX) - } - for p.Y > dst.Y { - p.append(UniqueY) - } - } -} - -func (p *path) append(t EditType) { - p.es = append(p.es, t) - switch t { - case Identity, Modified: - p.add(p.dir, p.dir) - case UniqueX: - p.add(p.dir, 0) - case UniqueY: - p.add(0, p.dir) - } - debug.Update() -} - -type point struct{ X, Y int } - -func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } - -// zigzag maps a consecutive sequence of integers to a zig-zag sequence. -// [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...] -func zigzag(x int) int { - if x&1 != 0 { - x = ^x - } - return x >> 1 -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go b/test/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go deleted file mode 100644 index d8e459c9b9..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package flags - -// Deterministic controls whether the output of Diff should be deterministic. -// This is only used for testing. -var Deterministic bool diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go b/test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go deleted file mode 100644 index 82d1d7fbf8..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.10 - -package flags - -// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. -const AtLeastGo110 = false diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go b/test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go deleted file mode 100644 index 8646f05293..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.10 - -package flags - -// AtLeastGo110 reports whether the Go toolchain is at least Go 1.10. -const AtLeastGo110 = true diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/function/func.go b/test/vendor/github.com/google/go-cmp/cmp/internal/function/func.go deleted file mode 100644 index d127d43623..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/function/func.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package function provides functionality for identifying function types. -package function - -import ( - "reflect" - "regexp" - "runtime" - "strings" -) - -type funcType int - -const ( - _ funcType = iota - - tbFunc // func(T) bool - ttbFunc // func(T, T) bool - trbFunc // func(T, R) bool - tibFunc // func(T, I) bool - trFunc // func(T) R - - Equal = ttbFunc // func(T, T) bool - EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool - Transformer = trFunc // func(T) R - ValueFilter = ttbFunc // func(T, T) bool - Less = ttbFunc // func(T, T) bool - ValuePredicate = tbFunc // func(T) bool - KeyValuePredicate = trbFunc // func(T, R) bool -) - -var boolType = reflect.TypeOf(true) - -// IsType reports whether the reflect.Type is of the specified function type. -func IsType(t reflect.Type, ft funcType) bool { - if t == nil || t.Kind() != reflect.Func || t.IsVariadic() { - return false - } - ni, no := t.NumIn(), t.NumOut() - switch ft { - case tbFunc: // func(T) bool - if ni == 1 && no == 1 && t.Out(0) == boolType { - return true - } - case ttbFunc: // func(T, T) bool - if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType { - return true - } - case trbFunc: // func(T, R) bool - if ni == 2 && no == 1 && t.Out(0) == boolType { - return true - } - case tibFunc: // func(T, I) bool - if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType { - return true - } - case trFunc: // func(T) R - if ni == 1 && no == 1 { - return true - } - } - return false -} - -var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`) - -// NameOf returns the name of the function value. -func NameOf(v reflect.Value) string { - fnc := runtime.FuncForPC(v.Pointer()) - if fnc == nil { - return "" - } - fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm" - - // Method closures have a "-fm" suffix. - fullName = strings.TrimSuffix(fullName, "-fm") - - var name string - for len(fullName) > 0 { - inParen := strings.HasSuffix(fullName, ")") - fullName = strings.TrimSuffix(fullName, ")") - - s := lastIdentRx.FindString(fullName) - if s == "" { - break - } - name = s + "." + name - fullName = strings.TrimSuffix(fullName, s) - - if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 { - fullName = fullName[:i] - } - fullName = strings.TrimSuffix(fullName, ".") - } - return strings.TrimSuffix(name, ".") -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/value/name.go b/test/vendor/github.com/google/go-cmp/cmp/internal/value/name.go deleted file mode 100644 index b6c12cefb4..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/value/name.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2020, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package value - -import ( - "reflect" - "strconv" -) - -// TypeString is nearly identical to reflect.Type.String, -// but has an additional option to specify that full type names be used. -func TypeString(t reflect.Type, qualified bool) string { - return string(appendTypeName(nil, t, qualified, false)) -} - -func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte { - // BUG: Go reflection provides no way to disambiguate two named types - // of the same name and within the same package, - // but declared within the namespace of different functions. - - // Named type. - if t.Name() != "" { - if qualified && t.PkgPath() != "" { - b = append(b, '"') - b = append(b, t.PkgPath()...) - b = append(b, '"') - b = append(b, '.') - b = append(b, t.Name()...) - } else { - b = append(b, t.String()...) - } - return b - } - - // Unnamed type. - switch k := t.Kind(); k { - case reflect.Bool, reflect.String, reflect.UnsafePointer, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, - reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: - b = append(b, k.String()...) - case reflect.Chan: - if t.ChanDir() == reflect.RecvDir { - b = append(b, "<-"...) - } - b = append(b, "chan"...) - if t.ChanDir() == reflect.SendDir { - b = append(b, "<-"...) - } - b = append(b, ' ') - b = appendTypeName(b, t.Elem(), qualified, false) - case reflect.Func: - if !elideFunc { - b = append(b, "func"...) - } - b = append(b, '(') - for i := 0; i < t.NumIn(); i++ { - if i > 0 { - b = append(b, ", "...) - } - if i == t.NumIn()-1 && t.IsVariadic() { - b = append(b, "..."...) - b = appendTypeName(b, t.In(i).Elem(), qualified, false) - } else { - b = appendTypeName(b, t.In(i), qualified, false) - } - } - b = append(b, ')') - switch t.NumOut() { - case 0: - // Do nothing - case 1: - b = append(b, ' ') - b = appendTypeName(b, t.Out(0), qualified, false) - default: - b = append(b, " ("...) - for i := 0; i < t.NumOut(); i++ { - if i > 0 { - b = append(b, ", "...) - } - b = appendTypeName(b, t.Out(i), qualified, false) - } - b = append(b, ')') - } - case reflect.Struct: - b = append(b, "struct{ "...) - for i := 0; i < t.NumField(); i++ { - if i > 0 { - b = append(b, "; "...) - } - sf := t.Field(i) - if !sf.Anonymous { - if qualified && sf.PkgPath != "" { - b = append(b, '"') - b = append(b, sf.PkgPath...) - b = append(b, '"') - b = append(b, '.') - } - b = append(b, sf.Name...) - b = append(b, ' ') - } - b = appendTypeName(b, sf.Type, qualified, false) - if sf.Tag != "" { - b = append(b, ' ') - b = strconv.AppendQuote(b, string(sf.Tag)) - } - } - if b[len(b)-1] == ' ' { - b = b[:len(b)-1] - } else { - b = append(b, ' ') - } - b = append(b, '}') - case reflect.Slice, reflect.Array: - b = append(b, '[') - if k == reflect.Array { - b = strconv.AppendUint(b, uint64(t.Len()), 10) - } - b = append(b, ']') - b = appendTypeName(b, t.Elem(), qualified, false) - case reflect.Map: - b = append(b, "map["...) - b = appendTypeName(b, t.Key(), qualified, false) - b = append(b, ']') - b = appendTypeName(b, t.Elem(), qualified, false) - case reflect.Ptr: - b = append(b, '*') - b = appendTypeName(b, t.Elem(), qualified, false) - case reflect.Interface: - b = append(b, "interface{ "...) - for i := 0; i < t.NumMethod(); i++ { - if i > 0 { - b = append(b, "; "...) - } - m := t.Method(i) - if qualified && m.PkgPath != "" { - b = append(b, '"') - b = append(b, m.PkgPath...) - b = append(b, '"') - b = append(b, '.') - } - b = append(b, m.Name...) - b = appendTypeName(b, m.Type, qualified, true) - } - if b[len(b)-1] == ' ' { - b = b[:len(b)-1] - } else { - b = append(b, ' ') - } - b = append(b, '}') - default: - panic("invalid kind: " + k.String()) - } - return b -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go b/test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go deleted file mode 100644 index 44f4a5afdd..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2018, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build purego - -package value - -import "reflect" - -// Pointer is an opaque typed pointer and is guaranteed to be comparable. -type Pointer struct { - p uintptr - t reflect.Type -} - -// PointerOf returns a Pointer from v, which must be a -// reflect.Ptr, reflect.Slice, or reflect.Map. -func PointerOf(v reflect.Value) Pointer { - // NOTE: Storing a pointer as an uintptr is technically incorrect as it - // assumes that the GC implementation does not use a moving collector. - return Pointer{v.Pointer(), v.Type()} -} - -// IsNil reports whether the pointer is nil. -func (p Pointer) IsNil() bool { - return p.p == 0 -} - -// Uintptr returns the pointer as a uintptr. -func (p Pointer) Uintptr() uintptr { - return p.p -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go b/test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go deleted file mode 100644 index a605953d46..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2018, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !purego - -package value - -import ( - "reflect" - "unsafe" -) - -// Pointer is an opaque typed pointer and is guaranteed to be comparable. -type Pointer struct { - p unsafe.Pointer - t reflect.Type -} - -// PointerOf returns a Pointer from v, which must be a -// reflect.Ptr, reflect.Slice, or reflect.Map. -func PointerOf(v reflect.Value) Pointer { - // The proper representation of a pointer is unsafe.Pointer, - // which is necessary if the GC ever uses a moving collector. - return Pointer{unsafe.Pointer(v.Pointer()), v.Type()} -} - -// IsNil reports whether the pointer is nil. -func (p Pointer) IsNil() bool { - return p.p == nil -} - -// Uintptr returns the pointer as a uintptr. -func (p Pointer) Uintptr() uintptr { - return uintptr(p.p) -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go b/test/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go deleted file mode 100644 index 98533b036c..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/value/sort.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package value - -import ( - "fmt" - "math" - "reflect" - "sort" -) - -// SortKeys sorts a list of map keys, deduplicating keys if necessary. -// The type of each value must be comparable. -func SortKeys(vs []reflect.Value) []reflect.Value { - if len(vs) == 0 { - return vs - } - - // Sort the map keys. - sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) }) - - // Deduplicate keys (fails for NaNs). - vs2 := vs[:1] - for _, v := range vs[1:] { - if isLess(vs2[len(vs2)-1], v) { - vs2 = append(vs2, v) - } - } - return vs2 -} - -// isLess is a generic function for sorting arbitrary map keys. -// The inputs must be of the same type and must be comparable. -func isLess(x, y reflect.Value) bool { - switch x.Type().Kind() { - case reflect.Bool: - return !x.Bool() && y.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return x.Int() < y.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return x.Uint() < y.Uint() - case reflect.Float32, reflect.Float64: - // NOTE: This does not sort -0 as less than +0 - // since Go maps treat -0 and +0 as equal keys. - fx, fy := x.Float(), y.Float() - return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy) - case reflect.Complex64, reflect.Complex128: - cx, cy := x.Complex(), y.Complex() - rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy) - if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) { - return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy) - } - return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry) - case reflect.Ptr, reflect.UnsafePointer, reflect.Chan: - return x.Pointer() < y.Pointer() - case reflect.String: - return x.String() < y.String() - case reflect.Array: - for i := 0; i < x.Len(); i++ { - if isLess(x.Index(i), y.Index(i)) { - return true - } - if isLess(y.Index(i), x.Index(i)) { - return false - } - } - return false - case reflect.Struct: - for i := 0; i < x.NumField(); i++ { - if isLess(x.Field(i), y.Field(i)) { - return true - } - if isLess(y.Field(i), x.Field(i)) { - return false - } - } - return false - case reflect.Interface: - vx, vy := x.Elem(), y.Elem() - if !vx.IsValid() || !vy.IsValid() { - return !vx.IsValid() && vy.IsValid() - } - tx, ty := vx.Type(), vy.Type() - if tx == ty { - return isLess(x.Elem(), y.Elem()) - } - if tx.Kind() != ty.Kind() { - return vx.Kind() < vy.Kind() - } - if tx.String() != ty.String() { - return tx.String() < ty.String() - } - if tx.PkgPath() != ty.PkgPath() { - return tx.PkgPath() < ty.PkgPath() - } - // This can happen in rare situations, so we fallback to just comparing - // the unique pointer for a reflect.Type. This guarantees deterministic - // ordering within a program, but it is obviously not stable. - return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer() - default: - // Must be Func, Map, or Slice; which are not comparable. - panic(fmt.Sprintf("%T is not comparable", x.Type())) - } -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go b/test/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go deleted file mode 100644 index 9147a29973..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package value - -import ( - "math" - "reflect" -) - -// IsZero reports whether v is the zero value. -// This does not rely on Interface and so can be used on unexported fields. -func IsZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Bool: - return v.Bool() == false - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return math.Float64bits(v.Float()) == 0 - case reflect.Complex64, reflect.Complex128: - return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0 - case reflect.String: - return v.String() == "" - case reflect.UnsafePointer: - return v.Pointer() == 0 - case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - return v.IsNil() - case reflect.Array: - for i := 0; i < v.Len(); i++ { - if !IsZero(v.Index(i)) { - return false - } - } - return true - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - if !IsZero(v.Field(i)) { - return false - } - } - return true - } - return false -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/options.go b/test/vendor/github.com/google/go-cmp/cmp/options.go deleted file mode 100644 index e57b9eb539..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/options.go +++ /dev/null @@ -1,552 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "fmt" - "reflect" - "regexp" - "strings" - - "github.com/google/go-cmp/cmp/internal/function" -) - -// Option configures for specific behavior of Equal and Diff. In particular, -// the fundamental Option functions (Ignore, Transformer, and Comparer), -// configure how equality is determined. -// -// The fundamental options may be composed with filters (FilterPath and -// FilterValues) to control the scope over which they are applied. -// -// The cmp/cmpopts package provides helper functions for creating options that -// may be used with Equal and Diff. -type Option interface { - // filter applies all filters and returns the option that remains. - // Each option may only read s.curPath and call s.callTTBFunc. - // - // An Options is returned only if multiple comparers or transformers - // can apply simultaneously and will only contain values of those types - // or sub-Options containing values of those types. - filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption -} - -// applicableOption represents the following types: -// Fundamental: ignore | validator | *comparer | *transformer -// Grouping: Options -type applicableOption interface { - Option - - // apply executes the option, which may mutate s or panic. - apply(s *state, vx, vy reflect.Value) -} - -// coreOption represents the following types: -// Fundamental: ignore | validator | *comparer | *transformer -// Filters: *pathFilter | *valuesFilter -type coreOption interface { - Option - isCore() -} - -type core struct{} - -func (core) isCore() {} - -// Options is a list of Option values that also satisfies the Option interface. -// Helper comparison packages may return an Options value when packing multiple -// Option values into a single Option. When this package processes an Options, -// it will be implicitly expanded into a flat list. -// -// Applying a filter on an Options is equivalent to applying that same filter -// on all individual options held within. -type Options []Option - -func (opts Options) filter(s *state, t reflect.Type, vx, vy reflect.Value) (out applicableOption) { - for _, opt := range opts { - switch opt := opt.filter(s, t, vx, vy); opt.(type) { - case ignore: - return ignore{} // Only ignore can short-circuit evaluation - case validator: - out = validator{} // Takes precedence over comparer or transformer - case *comparer, *transformer, Options: - switch out.(type) { - case nil: - out = opt - case validator: - // Keep validator - case *comparer, *transformer, Options: - out = Options{out, opt} // Conflicting comparers or transformers - } - } - } - return out -} - -func (opts Options) apply(s *state, _, _ reflect.Value) { - const warning = "ambiguous set of applicable options" - const help = "consider using filters to ensure at most one Comparer or Transformer may apply" - var ss []string - for _, opt := range flattenOptions(nil, opts) { - ss = append(ss, fmt.Sprint(opt)) - } - set := strings.Join(ss, "\n\t") - panic(fmt.Sprintf("%s at %#v:\n\t%s\n%s", warning, s.curPath, set, help)) -} - -func (opts Options) String() string { - var ss []string - for _, opt := range opts { - ss = append(ss, fmt.Sprint(opt)) - } - return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) -} - -// FilterPath returns a new Option where opt is only evaluated if filter f -// returns true for the current Path in the value tree. -// -// This filter is called even if a slice element or map entry is missing and -// provides an opportunity to ignore such cases. The filter function must be -// symmetric such that the filter result is identical regardless of whether the -// missing value is from x or y. -// -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. -func FilterPath(f func(Path) bool, opt Option) Option { - if f == nil { - panic("invalid path filter function") - } - if opt := normalizeOption(opt); opt != nil { - return &pathFilter{fnc: f, opt: opt} - } - return nil -} - -type pathFilter struct { - core - fnc func(Path) bool - opt Option -} - -func (f pathFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { - if f.fnc(s.curPath) { - return f.opt.filter(s, t, vx, vy) - } - return nil -} - -func (f pathFilter) String() string { - return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) -} - -// FilterValues returns a new Option where opt is only evaluated if filter f, -// which is a function of the form "func(T, T) bool", returns true for the -// current pair of values being compared. If either value is invalid or -// the type of the values is not assignable to T, then this filter implicitly -// returns false. -// -// The filter function must be -// symmetric (i.e., agnostic to the order of the inputs) and -// deterministic (i.e., produces the same result when given the same inputs). -// If T is an interface, it is possible that f is called with two values with -// different concrete types that both implement T. -// -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. -func FilterValues(f interface{}, opt Option) Option { - v := reflect.ValueOf(f) - if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { - panic(fmt.Sprintf("invalid values filter function: %T", f)) - } - if opt := normalizeOption(opt); opt != nil { - vf := &valuesFilter{fnc: v, opt: opt} - if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { - vf.typ = ti - } - return vf - } - return nil -} - -type valuesFilter struct { - core - typ reflect.Type // T - fnc reflect.Value // func(T, T) bool - opt Option -} - -func (f valuesFilter) filter(s *state, t reflect.Type, vx, vy reflect.Value) applicableOption { - if !vx.IsValid() || !vx.CanInterface() || !vy.IsValid() || !vy.CanInterface() { - return nil - } - if (f.typ == nil || t.AssignableTo(f.typ)) && s.callTTBFunc(f.fnc, vx, vy) { - return f.opt.filter(s, t, vx, vy) - } - return nil -} - -func (f valuesFilter) String() string { - return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) -} - -// Ignore is an Option that causes all comparisons to be ignored. -// This value is intended to be combined with FilterPath or FilterValues. -// It is an error to pass an unfiltered Ignore option to Equal. -func Ignore() Option { return ignore{} } - -type ignore struct{ core } - -func (ignore) isFiltered() bool { return false } -func (ignore) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { return ignore{} } -func (ignore) apply(s *state, _, _ reflect.Value) { s.report(true, reportByIgnore) } -func (ignore) String() string { return "Ignore()" } - -// validator is a sentinel Option type to indicate that some options could not -// be evaluated due to unexported fields, missing slice elements, or -// missing map entries. Both values are validator only for unexported fields. -type validator struct{ core } - -func (validator) filter(_ *state, _ reflect.Type, vx, vy reflect.Value) applicableOption { - if !vx.IsValid() || !vy.IsValid() { - return validator{} - } - if !vx.CanInterface() || !vy.CanInterface() { - return validator{} - } - return nil -} -func (validator) apply(s *state, vx, vy reflect.Value) { - // Implies missing slice element or map entry. - if !vx.IsValid() || !vy.IsValid() { - s.report(vx.IsValid() == vy.IsValid(), 0) - return - } - - // Unable to Interface implies unexported field without visibility access. - if !vx.CanInterface() || !vy.CanInterface() { - help := "consider using a custom Comparer; if you control the implementation of type, you can also consider using an Exporter, AllowUnexported, or cmpopts.IgnoreUnexported" - var name string - if t := s.curPath.Index(-2).Type(); t.Name() != "" { - // Named type with unexported fields. - name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType - if _, ok := reflect.New(t).Interface().(error); ok { - help = "consider using cmpopts.EquateErrors to compare error values" - } - } else { - // Unnamed type with unexported fields. Derive PkgPath from field. - var pkgPath string - for i := 0; i < t.NumField() && pkgPath == ""; i++ { - pkgPath = t.Field(i).PkgPath - } - name = fmt.Sprintf("%q.(%v)", pkgPath, t.String()) // e.g., "path/to/package".(struct { a int }) - } - panic(fmt.Sprintf("cannot handle unexported field at %#v:\n\t%v\n%s", s.curPath, name, help)) - } - - panic("not reachable") -} - -// identRx represents a valid identifier according to the Go specification. -const identRx = `[_\p{L}][_\p{L}\p{N}]*` - -var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) - -// Transformer returns an Option that applies a transformation function that -// converts values of a certain type into that of another. -// -// The transformer f must be a function "func(T) R" that converts values of -// type T to those of type R and is implicitly filtered to input values -// assignable to T. The transformer must not mutate T in any way. -// -// To help prevent some cases of infinite recursive cycles applying the -// same transform to the output of itself (e.g., in the case where the -// input and output types are the same), an implicit filter is added such that -// a transformer is applicable only if that exact transformer is not already -// in the tail of the Path since the last non-Transform step. -// For situations where the implicit filter is still insufficient, -// consider using cmpopts.AcyclicTransformer, which adds a filter -// to prevent the transformer from being recursively applied upon itself. -// -// The name is a user provided label that is used as the Transform.Name in the -// transformation PathStep (and eventually shown in the Diff output). -// The name must be a valid identifier or qualified identifier in Go syntax. -// If empty, an arbitrary name is used. -func Transformer(name string, f interface{}) Option { - v := reflect.ValueOf(f) - if !function.IsType(v.Type(), function.Transformer) || v.IsNil() { - panic(fmt.Sprintf("invalid transformer function: %T", f)) - } - if name == "" { - name = function.NameOf(v) - if !identsRx.MatchString(name) { - name = "λ" // Lambda-symbol as placeholder name - } - } else if !identsRx.MatchString(name) { - panic(fmt.Sprintf("invalid name: %q", name)) - } - tr := &transformer{name: name, fnc: reflect.ValueOf(f)} - if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { - tr.typ = ti - } - return tr -} - -type transformer struct { - core - name string - typ reflect.Type // T - fnc reflect.Value // func(T) R -} - -func (tr *transformer) isFiltered() bool { return tr.typ != nil } - -func (tr *transformer) filter(s *state, t reflect.Type, _, _ reflect.Value) applicableOption { - for i := len(s.curPath) - 1; i >= 0; i-- { - if t, ok := s.curPath[i].(Transform); !ok { - break // Hit most recent non-Transform step - } else if tr == t.trans { - return nil // Cannot directly use same Transform - } - } - if tr.typ == nil || t.AssignableTo(tr.typ) { - return tr - } - return nil -} - -func (tr *transformer) apply(s *state, vx, vy reflect.Value) { - step := Transform{&transform{pathStep{typ: tr.fnc.Type().Out(0)}, tr}} - vvx := s.callTRFunc(tr.fnc, vx, step) - vvy := s.callTRFunc(tr.fnc, vy, step) - step.vx, step.vy = vvx, vvy - s.compareAny(step) -} - -func (tr transformer) String() string { - return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) -} - -// Comparer returns an Option that determines whether two values are equal -// to each other. -// -// The comparer f must be a function "func(T, T) bool" and is implicitly -// filtered to input values assignable to T. If T is an interface, it is -// possible that f is called with two values of different concrete types that -// both implement T. -// -// The equality function must be: -// • Symmetric: equal(x, y) == equal(y, x) -// • Deterministic: equal(x, y) == equal(x, y) -// • Pure: equal(x, y) does not modify x or y -func Comparer(f interface{}) Option { - v := reflect.ValueOf(f) - if !function.IsType(v.Type(), function.Equal) || v.IsNil() { - panic(fmt.Sprintf("invalid comparer function: %T", f)) - } - cm := &comparer{fnc: v} - if ti := v.Type().In(0); ti.Kind() != reflect.Interface || ti.NumMethod() > 0 { - cm.typ = ti - } - return cm -} - -type comparer struct { - core - typ reflect.Type // T - fnc reflect.Value // func(T, T) bool -} - -func (cm *comparer) isFiltered() bool { return cm.typ != nil } - -func (cm *comparer) filter(_ *state, t reflect.Type, _, _ reflect.Value) applicableOption { - if cm.typ == nil || t.AssignableTo(cm.typ) { - return cm - } - return nil -} - -func (cm *comparer) apply(s *state, vx, vy reflect.Value) { - eq := s.callTTBFunc(cm.fnc, vx, vy) - s.report(eq, reportByFunc) -} - -func (cm comparer) String() string { - return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) -} - -// Exporter returns an Option that specifies whether Equal is allowed to -// introspect into the unexported fields of certain struct types. -// -// Users of this option must understand that comparing on unexported fields -// from external packages is not safe since changes in the internal -// implementation of some external package may cause the result of Equal -// to unexpectedly change. However, it may be valid to use this option on types -// defined in an internal package where the semantic meaning of an unexported -// field is in the control of the user. -// -// In many cases, a custom Comparer should be used instead that defines -// equality as a function of the public API of a type rather than the underlying -// unexported implementation. -// -// For example, the reflect.Type documentation defines equality to be determined -// by the == operator on the interface (essentially performing a shallow pointer -// comparison) and most attempts to compare *regexp.Regexp types are interested -// in only checking that the regular expression strings are equal. -// Both of these are accomplished using Comparers: -// -// Comparer(func(x, y reflect.Type) bool { return x == y }) -// Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() }) -// -// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore -// all unexported fields on specified struct types. -func Exporter(f func(reflect.Type) bool) Option { - if !supportExporters { - panic("Exporter is not supported on purego builds") - } - return exporter(f) -} - -type exporter func(reflect.Type) bool - -func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { - panic("not implemented") -} - -// AllowUnexported returns an Options that allows Equal to forcibly introspect -// unexported fields of the specified struct types. -// -// See Exporter for the proper use of this option. -func AllowUnexported(types ...interface{}) Option { - m := make(map[reflect.Type]bool) - for _, typ := range types { - t := reflect.TypeOf(typ) - if t.Kind() != reflect.Struct { - panic(fmt.Sprintf("invalid struct type: %T", typ)) - } - m[t] = true - } - return exporter(func(t reflect.Type) bool { return m[t] }) -} - -// Result represents the comparison result for a single node and -// is provided by cmp when calling Result (see Reporter). -type Result struct { - _ [0]func() // Make Result incomparable - flags resultFlags -} - -// Equal reports whether the node was determined to be equal or not. -// As a special case, ignored nodes are considered equal. -func (r Result) Equal() bool { - return r.flags&(reportEqual|reportByIgnore) != 0 -} - -// ByIgnore reports whether the node is equal because it was ignored. -// This never reports true if Equal reports false. -func (r Result) ByIgnore() bool { - return r.flags&reportByIgnore != 0 -} - -// ByMethod reports whether the Equal method determined equality. -func (r Result) ByMethod() bool { - return r.flags&reportByMethod != 0 -} - -// ByFunc reports whether a Comparer function determined equality. -func (r Result) ByFunc() bool { - return r.flags&reportByFunc != 0 -} - -// ByCycle reports whether a reference cycle was detected. -func (r Result) ByCycle() bool { - return r.flags&reportByCycle != 0 -} - -type resultFlags uint - -const ( - _ resultFlags = (1 << iota) / 2 - - reportEqual - reportUnequal - reportByIgnore - reportByMethod - reportByFunc - reportByCycle -) - -// Reporter is an Option that can be passed to Equal. When Equal traverses -// the value trees, it calls PushStep as it descends into each node in the -// tree and PopStep as it ascend out of the node. The leaves of the tree are -// either compared (determined to be equal or not equal) or ignored and reported -// as such by calling the Report method. -func Reporter(r interface { - // PushStep is called when a tree-traversal operation is performed. - // The PathStep itself is only valid until the step is popped. - // The PathStep.Values are valid for the duration of the entire traversal - // and must not be mutated. - // - // Equal always calls PushStep at the start to provide an operation-less - // PathStep used to report the root values. - // - // Within a slice, the exact set of inserted, removed, or modified elements - // is unspecified and may change in future implementations. - // The entries of a map are iterated through in an unspecified order. - PushStep(PathStep) - - // Report is called exactly once on leaf nodes to report whether the - // comparison identified the node as equal, unequal, or ignored. - // A leaf node is one that is immediately preceded by and followed by - // a pair of PushStep and PopStep calls. - Report(Result) - - // PopStep ascends back up the value tree. - // There is always a matching pop call for every push call. - PopStep() -}) Option { - return reporter{r} -} - -type reporter struct{ reporterIface } -type reporterIface interface { - PushStep(PathStep) - Report(Result) - PopStep() -} - -func (reporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableOption { - panic("not implemented") -} - -// normalizeOption normalizes the input options such that all Options groups -// are flattened and groups with a single element are reduced to that element. -// Only coreOptions and Options containing coreOptions are allowed. -func normalizeOption(src Option) Option { - switch opts := flattenOptions(nil, Options{src}); len(opts) { - case 0: - return nil - case 1: - return opts[0] - default: - return opts - } -} - -// flattenOptions copies all options in src to dst as a flat list. -// Only coreOptions and Options containing coreOptions are allowed. -func flattenOptions(dst, src Options) Options { - for _, opt := range src { - switch opt := opt.(type) { - case nil: - continue - case Options: - dst = flattenOptions(dst, opt) - case coreOption: - dst = append(dst, opt) - default: - panic(fmt.Sprintf("invalid option type: %T", opt)) - } - } - return dst -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/path.go b/test/vendor/github.com/google/go-cmp/cmp/path.go deleted file mode 100644 index f01eff318c..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/path.go +++ /dev/null @@ -1,378 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "fmt" - "reflect" - "strings" - "unicode" - "unicode/utf8" - - "github.com/google/go-cmp/cmp/internal/value" -) - -// Path is a list of PathSteps describing the sequence of operations to get -// from some root type to the current position in the value tree. -// The first Path element is always an operation-less PathStep that exists -// simply to identify the initial type. -// -// When traversing structs with embedded structs, the embedded struct will -// always be accessed as a field before traversing the fields of the -// embedded struct themselves. That is, an exported field from the -// embedded struct will never be accessed directly from the parent struct. -type Path []PathStep - -// PathStep is a union-type for specific operations to traverse -// a value's tree structure. Users of this package never need to implement -// these types as values of this type will be returned by this package. -// -// Implementations of this interface are -// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform. -type PathStep interface { - String() string - - // Type is the resulting type after performing the path step. - Type() reflect.Type - - // Values is the resulting values after performing the path step. - // The type of each valid value is guaranteed to be identical to Type. - // - // In some cases, one or both may be invalid or have restrictions: - // • For StructField, both are not interface-able if the current field - // is unexported and the struct type is not explicitly permitted by - // an Exporter to traverse unexported fields. - // • For SliceIndex, one may be invalid if an element is missing from - // either the x or y slice. - // • For MapIndex, one may be invalid if an entry is missing from - // either the x or y map. - // - // The provided values must not be mutated. - Values() (vx, vy reflect.Value) -} - -var ( - _ PathStep = StructField{} - _ PathStep = SliceIndex{} - _ PathStep = MapIndex{} - _ PathStep = Indirect{} - _ PathStep = TypeAssertion{} - _ PathStep = Transform{} -) - -func (pa *Path) push(s PathStep) { - *pa = append(*pa, s) -} - -func (pa *Path) pop() { - *pa = (*pa)[:len(*pa)-1] -} - -// Last returns the last PathStep in the Path. -// If the path is empty, this returns a non-nil PathStep that reports a nil Type. -func (pa Path) Last() PathStep { - return pa.Index(-1) -} - -// Index returns the ith step in the Path and supports negative indexing. -// A negative index starts counting from the tail of the Path such that -1 -// refers to the last step, -2 refers to the second-to-last step, and so on. -// If index is invalid, this returns a non-nil PathStep that reports a nil Type. -func (pa Path) Index(i int) PathStep { - if i < 0 { - i = len(pa) + i - } - if i < 0 || i >= len(pa) { - return pathStep{} - } - return pa[i] -} - -// String returns the simplified path to a node. -// The simplified path only contains struct field accesses. -// -// For example: -// MyMap.MySlices.MyField -func (pa Path) String() string { - var ss []string - for _, s := range pa { - if _, ok := s.(StructField); ok { - ss = append(ss, s.String()) - } - } - return strings.TrimPrefix(strings.Join(ss, ""), ".") -} - -// GoString returns the path to a specific node using Go syntax. -// -// For example: -// (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField -func (pa Path) GoString() string { - var ssPre, ssPost []string - var numIndirect int - for i, s := range pa { - var nextStep PathStep - if i+1 < len(pa) { - nextStep = pa[i+1] - } - switch s := s.(type) { - case Indirect: - numIndirect++ - pPre, pPost := "(", ")" - switch nextStep.(type) { - case Indirect: - continue // Next step is indirection, so let them batch up - case StructField: - numIndirect-- // Automatic indirection on struct fields - case nil: - pPre, pPost = "", "" // Last step; no need for parenthesis - } - if numIndirect > 0 { - ssPre = append(ssPre, pPre+strings.Repeat("*", numIndirect)) - ssPost = append(ssPost, pPost) - } - numIndirect = 0 - continue - case Transform: - ssPre = append(ssPre, s.trans.name+"(") - ssPost = append(ssPost, ")") - continue - } - ssPost = append(ssPost, s.String()) - } - for i, j := 0, len(ssPre)-1; i < j; i, j = i+1, j-1 { - ssPre[i], ssPre[j] = ssPre[j], ssPre[i] - } - return strings.Join(ssPre, "") + strings.Join(ssPost, "") -} - -type pathStep struct { - typ reflect.Type - vx, vy reflect.Value -} - -func (ps pathStep) Type() reflect.Type { return ps.typ } -func (ps pathStep) Values() (vx, vy reflect.Value) { return ps.vx, ps.vy } -func (ps pathStep) String() string { - if ps.typ == nil { - return "" - } - s := ps.typ.String() - if s == "" || strings.ContainsAny(s, "{}\n") { - return "root" // Type too simple or complex to print - } - return fmt.Sprintf("{%s}", s) -} - -// StructField represents a struct field access on a field called Name. -type StructField struct{ *structField } -type structField struct { - pathStep - name string - idx int - - // These fields are used for forcibly accessing an unexported field. - // pvx, pvy, and field are only valid if unexported is true. - unexported bool - mayForce bool // Forcibly allow visibility - paddr bool // Was parent addressable? - pvx, pvy reflect.Value // Parent values (always addressible) - field reflect.StructField // Field information -} - -func (sf StructField) Type() reflect.Type { return sf.typ } -func (sf StructField) Values() (vx, vy reflect.Value) { - if !sf.unexported { - return sf.vx, sf.vy // CanInterface reports true - } - - // Forcibly obtain read-write access to an unexported struct field. - if sf.mayForce { - vx = retrieveUnexportedField(sf.pvx, sf.field, sf.paddr) - vy = retrieveUnexportedField(sf.pvy, sf.field, sf.paddr) - return vx, vy // CanInterface reports true - } - return sf.vx, sf.vy // CanInterface reports false -} -func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } - -// Name is the field name. -func (sf StructField) Name() string { return sf.name } - -// Index is the index of the field in the parent struct type. -// See reflect.Type.Field. -func (sf StructField) Index() int { return sf.idx } - -// SliceIndex is an index operation on a slice or array at some index Key. -type SliceIndex struct{ *sliceIndex } -type sliceIndex struct { - pathStep - xkey, ykey int - isSlice bool // False for reflect.Array -} - -func (si SliceIndex) Type() reflect.Type { return si.typ } -func (si SliceIndex) Values() (vx, vy reflect.Value) { return si.vx, si.vy } -func (si SliceIndex) String() string { - switch { - case si.xkey == si.ykey: - return fmt.Sprintf("[%d]", si.xkey) - case si.ykey == -1: - // [5->?] means "I don't know where X[5] went" - return fmt.Sprintf("[%d->?]", si.xkey) - case si.xkey == -1: - // [?->3] means "I don't know where Y[3] came from" - return fmt.Sprintf("[?->%d]", si.ykey) - default: - // [5->3] means "X[5] moved to Y[3]" - return fmt.Sprintf("[%d->%d]", si.xkey, si.ykey) - } -} - -// Key is the index key; it may return -1 if in a split state -func (si SliceIndex) Key() int { - if si.xkey != si.ykey { - return -1 - } - return si.xkey -} - -// SplitKeys are the indexes for indexing into slices in the -// x and y values, respectively. These indexes may differ due to the -// insertion or removal of an element in one of the slices, causing -// all of the indexes to be shifted. If an index is -1, then that -// indicates that the element does not exist in the associated slice. -// -// Key is guaranteed to return -1 if and only if the indexes returned -// by SplitKeys are not the same. SplitKeys will never return -1 for -// both indexes. -func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } - -// MapIndex is an index operation on a map at some index Key. -type MapIndex struct{ *mapIndex } -type mapIndex struct { - pathStep - key reflect.Value -} - -func (mi MapIndex) Type() reflect.Type { return mi.typ } -func (mi MapIndex) Values() (vx, vy reflect.Value) { return mi.vx, mi.vy } -func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", mi.key) } - -// Key is the value of the map key. -func (mi MapIndex) Key() reflect.Value { return mi.key } - -// Indirect represents pointer indirection on the parent type. -type Indirect struct{ *indirect } -type indirect struct { - pathStep -} - -func (in Indirect) Type() reflect.Type { return in.typ } -func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } -func (in Indirect) String() string { return "*" } - -// TypeAssertion represents a type assertion on an interface. -type TypeAssertion struct{ *typeAssertion } -type typeAssertion struct { - pathStep -} - -func (ta TypeAssertion) Type() reflect.Type { return ta.typ } -func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } -func (ta TypeAssertion) String() string { return fmt.Sprintf(".(%v)", ta.typ) } - -// Transform is a transformation from the parent type to the current type. -type Transform struct{ *transform } -type transform struct { - pathStep - trans *transformer -} - -func (tf Transform) Type() reflect.Type { return tf.typ } -func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } -func (tf Transform) String() string { return fmt.Sprintf("%s()", tf.trans.name) } - -// Name is the name of the Transformer. -func (tf Transform) Name() string { return tf.trans.name } - -// Func is the function pointer to the transformer function. -func (tf Transform) Func() reflect.Value { return tf.trans.fnc } - -// Option returns the originally constructed Transformer option. -// The == operator can be used to detect the exact option used. -func (tf Transform) Option() Option { return tf.trans } - -// pointerPath represents a dual-stack of pointers encountered when -// recursively traversing the x and y values. This data structure supports -// detection of cycles and determining whether the cycles are equal. -// In Go, cycles can occur via pointers, slices, and maps. -// -// The pointerPath uses a map to represent a stack; where descension into a -// pointer pushes the address onto the stack, and ascension from a pointer -// pops the address from the stack. Thus, when traversing into a pointer from -// reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles -// by checking whether the pointer has already been visited. The cycle detection -// uses a separate stack for the x and y values. -// -// If a cycle is detected we need to determine whether the two pointers -// should be considered equal. The definition of equality chosen by Equal -// requires two graphs to have the same structure. To determine this, both the -// x and y values must have a cycle where the previous pointers were also -// encountered together as a pair. -// -// Semantically, this is equivalent to augmenting Indirect, SliceIndex, and -// MapIndex with pointer information for the x and y values. -// Suppose px and py are two pointers to compare, we then search the -// Path for whether px was ever encountered in the Path history of x, and -// similarly so with py. If either side has a cycle, the comparison is only -// equal if both px and py have a cycle resulting from the same PathStep. -// -// Using a map as a stack is more performant as we can perform cycle detection -// in O(1) instead of O(N) where N is len(Path). -type pointerPath struct { - // mx is keyed by x pointers, where the value is the associated y pointer. - mx map[value.Pointer]value.Pointer - // my is keyed by y pointers, where the value is the associated x pointer. - my map[value.Pointer]value.Pointer -} - -func (p *pointerPath) Init() { - p.mx = make(map[value.Pointer]value.Pointer) - p.my = make(map[value.Pointer]value.Pointer) -} - -// Push indicates intent to descend into pointers vx and vy where -// visited reports whether either has been seen before. If visited before, -// equal reports whether both pointers were encountered together. -// Pop must be called if and only if the pointers were never visited. -// -// The pointers vx and vy must be a reflect.Ptr, reflect.Slice, or reflect.Map -// and be non-nil. -func (p pointerPath) Push(vx, vy reflect.Value) (equal, visited bool) { - px := value.PointerOf(vx) - py := value.PointerOf(vy) - _, ok1 := p.mx[px] - _, ok2 := p.my[py] - if ok1 || ok2 { - equal = p.mx[px] == py && p.my[py] == px // Pointers paired together - return equal, true - } - p.mx[px] = py - p.my[py] = px - return false, false -} - -// Pop ascends from pointers vx and vy. -func (p pointerPath) Pop(vx, vy reflect.Value) { - delete(p.mx, value.PointerOf(vx)) - delete(p.my, value.PointerOf(vy)) -} - -// isExported reports whether the identifier is exported. -func isExported(id string) bool { - r, _ := utf8.DecodeRuneInString(id) - return unicode.IsUpper(r) -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/report.go b/test/vendor/github.com/google/go-cmp/cmp/report.go deleted file mode 100644 index f43cd12eb5..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2017, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -// defaultReporter implements the reporter interface. -// -// As Equal serially calls the PushStep, Report, and PopStep methods, the -// defaultReporter constructs a tree-based representation of the compared value -// and the result of each comparison (see valueNode). -// -// When the String method is called, the FormatDiff method transforms the -// valueNode tree into a textNode tree, which is a tree-based representation -// of the textual output (see textNode). -// -// Lastly, the textNode.String method produces the final report as a string. -type defaultReporter struct { - root *valueNode - curr *valueNode -} - -func (r *defaultReporter) PushStep(ps PathStep) { - r.curr = r.curr.PushStep(ps) - if r.root == nil { - r.root = r.curr - } -} -func (r *defaultReporter) Report(rs Result) { - r.curr.Report(rs) -} -func (r *defaultReporter) PopStep() { - r.curr = r.curr.PopStep() -} - -// String provides a full report of the differences detected as a structured -// literal in pseudo-Go syntax. String may only be called after the entire tree -// has been traversed. -func (r *defaultReporter) String() string { - assert(r.root != nil && r.curr == nil) - if r.root.NumDiff == 0 { - return "" - } - ptrs := new(pointerReferences) - text := formatOptions{}.FormatDiff(r.root, ptrs) - resolveReferences(text) - return text.String() -} - -func assert(ok bool) { - if !ok { - panic("assertion failure") - } -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/report_compare.go b/test/vendor/github.com/google/go-cmp/cmp/report_compare.go deleted file mode 100644 index 104bb30538..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report_compare.go +++ /dev/null @@ -1,432 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "fmt" - "reflect" - - "github.com/google/go-cmp/cmp/internal/value" -) - -// numContextRecords is the number of surrounding equal records to print. -const numContextRecords = 2 - -type diffMode byte - -const ( - diffUnknown diffMode = 0 - diffIdentical diffMode = ' ' - diffRemoved diffMode = '-' - diffInserted diffMode = '+' -) - -type typeMode int - -const ( - // emitType always prints the type. - emitType typeMode = iota - // elideType never prints the type. - elideType - // autoType prints the type only for composite kinds - // (i.e., structs, slices, arrays, and maps). - autoType -) - -type formatOptions struct { - // DiffMode controls the output mode of FormatDiff. - // - // If diffUnknown, then produce a diff of the x and y values. - // If diffIdentical, then emit values as if they were equal. - // If diffRemoved, then only emit x values (ignoring y values). - // If diffInserted, then only emit y values (ignoring x values). - DiffMode diffMode - - // TypeMode controls whether to print the type for the current node. - // - // As a general rule of thumb, we always print the type of the next node - // after an interface, and always elide the type of the next node after - // a slice or map node. - TypeMode typeMode - - // formatValueOptions are options specific to printing reflect.Values. - formatValueOptions -} - -func (opts formatOptions) WithDiffMode(d diffMode) formatOptions { - opts.DiffMode = d - return opts -} -func (opts formatOptions) WithTypeMode(t typeMode) formatOptions { - opts.TypeMode = t - return opts -} -func (opts formatOptions) WithVerbosity(level int) formatOptions { - opts.VerbosityLevel = level - opts.LimitVerbosity = true - return opts -} -func (opts formatOptions) verbosity() uint { - switch { - case opts.VerbosityLevel < 0: - return 0 - case opts.VerbosityLevel > 16: - return 16 // some reasonable maximum to avoid shift overflow - default: - return uint(opts.VerbosityLevel) - } -} - -const maxVerbosityPreset = 6 - -// verbosityPreset modifies the verbosity settings given an index -// between 0 and maxVerbosityPreset, inclusive. -func verbosityPreset(opts formatOptions, i int) formatOptions { - opts.VerbosityLevel = int(opts.verbosity()) + 2*i - if i > 0 { - opts.AvoidStringer = true - } - if i >= maxVerbosityPreset { - opts.PrintAddresses = true - opts.QualifiedNames = true - } - return opts -} - -// FormatDiff converts a valueNode tree into a textNode tree, where the later -// is a textual representation of the differences detected in the former. -func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) { - if opts.DiffMode == diffIdentical { - opts = opts.WithVerbosity(1) - } else if opts.verbosity() < 3 { - opts = opts.WithVerbosity(3) - } - - // Check whether we have specialized formatting for this node. - // This is not necessary, but helpful for producing more readable outputs. - if opts.CanFormatDiffSlice(v) { - return opts.FormatDiffSlice(v) - } - - var parentKind reflect.Kind - if v.parent != nil && v.parent.TransformerName == "" { - parentKind = v.parent.Type.Kind() - } - - // For leaf nodes, format the value based on the reflect.Values alone. - if v.MaxDepth == 0 { - switch opts.DiffMode { - case diffUnknown, diffIdentical: - // Format Equal. - if v.NumDiff == 0 { - outx := opts.FormatValue(v.ValueX, parentKind, ptrs) - outy := opts.FormatValue(v.ValueY, parentKind, ptrs) - if v.NumIgnored > 0 && v.NumSame == 0 { - return textEllipsis - } else if outx.Len() < outy.Len() { - return outx - } else { - return outy - } - } - - // Format unequal. - assert(opts.DiffMode == diffUnknown) - var list textList - outx := opts.WithTypeMode(elideType).FormatValue(v.ValueX, parentKind, ptrs) - outy := opts.WithTypeMode(elideType).FormatValue(v.ValueY, parentKind, ptrs) - for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ { - opts2 := verbosityPreset(opts, i).WithTypeMode(elideType) - outx = opts2.FormatValue(v.ValueX, parentKind, ptrs) - outy = opts2.FormatValue(v.ValueY, parentKind, ptrs) - } - if outx != nil { - list = append(list, textRecord{Diff: '-', Value: outx}) - } - if outy != nil { - list = append(list, textRecord{Diff: '+', Value: outy}) - } - return opts.WithTypeMode(emitType).FormatType(v.Type, list) - case diffRemoved: - return opts.FormatValue(v.ValueX, parentKind, ptrs) - case diffInserted: - return opts.FormatValue(v.ValueY, parentKind, ptrs) - default: - panic("invalid diff mode") - } - } - - // Register slice element to support cycle detection. - if parentKind == reflect.Slice { - ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, true) - defer ptrs.Pop() - defer func() { out = wrapTrunkReferences(ptrRefs, out) }() - } - - // Descend into the child value node. - if v.TransformerName != "" { - out := opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs) - out = &textWrap{Prefix: "Inverse(" + v.TransformerName + ", ", Value: out, Suffix: ")"} - return opts.FormatType(v.Type, out) - } else { - switch k := v.Type.Kind(); k { - case reflect.Struct, reflect.Array, reflect.Slice: - out = opts.formatDiffList(v.Records, k, ptrs) - out = opts.FormatType(v.Type, out) - case reflect.Map: - // Register map to support cycle detection. - ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false) - defer ptrs.Pop() - - out = opts.formatDiffList(v.Records, k, ptrs) - out = wrapTrunkReferences(ptrRefs, out) - out = opts.FormatType(v.Type, out) - case reflect.Ptr: - // Register pointer to support cycle detection. - ptrRefs := ptrs.PushPair(v.ValueX, v.ValueY, opts.DiffMode, false) - defer ptrs.Pop() - - out = opts.FormatDiff(v.Value, ptrs) - out = wrapTrunkReferences(ptrRefs, out) - out = &textWrap{Prefix: "&", Value: out} - case reflect.Interface: - out = opts.WithTypeMode(emitType).FormatDiff(v.Value, ptrs) - default: - panic(fmt.Sprintf("%v cannot have children", k)) - } - return out - } -} - -func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, ptrs *pointerReferences) textNode { - // Derive record name based on the data structure kind. - var name string - var formatKey func(reflect.Value) string - switch k { - case reflect.Struct: - name = "field" - opts = opts.WithTypeMode(autoType) - formatKey = func(v reflect.Value) string { return v.String() } - case reflect.Slice, reflect.Array: - name = "element" - opts = opts.WithTypeMode(elideType) - formatKey = func(reflect.Value) string { return "" } - case reflect.Map: - name = "entry" - opts = opts.WithTypeMode(elideType) - formatKey = func(v reflect.Value) string { return formatMapKey(v, false, ptrs) } - } - - maxLen := -1 - if opts.LimitVerbosity { - if opts.DiffMode == diffIdentical { - maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... - } else { - maxLen = (1 << opts.verbosity()) << 1 // 2, 4, 8, 16, 32, 64, etc... - } - opts.VerbosityLevel-- - } - - // Handle unification. - switch opts.DiffMode { - case diffIdentical, diffRemoved, diffInserted: - var list textList - var deferredEllipsis bool // Add final "..." to indicate records were dropped - for _, r := range recs { - if len(list) == maxLen { - deferredEllipsis = true - break - } - - // Elide struct fields that are zero value. - if k == reflect.Struct { - var isZero bool - switch opts.DiffMode { - case diffIdentical: - isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) - case diffRemoved: - isZero = value.IsZero(r.Value.ValueX) - case diffInserted: - isZero = value.IsZero(r.Value.ValueY) - } - if isZero { - continue - } - } - // Elide ignored nodes. - if r.Value.NumIgnored > 0 && r.Value.NumSame+r.Value.NumDiff == 0 { - deferredEllipsis = !(k == reflect.Slice || k == reflect.Array) - if !deferredEllipsis { - list.AppendEllipsis(diffStats{}) - } - continue - } - if out := opts.FormatDiff(r.Value, ptrs); out != nil { - list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) - } - } - if deferredEllipsis { - list.AppendEllipsis(diffStats{}) - } - return &textWrap{Prefix: "{", Value: list, Suffix: "}"} - case diffUnknown: - default: - panic("invalid diff mode") - } - - // Handle differencing. - var numDiffs int - var list textList - var keys []reflect.Value // invariant: len(list) == len(keys) - groups := coalesceAdjacentRecords(name, recs) - maxGroup := diffStats{Name: name} - for i, ds := range groups { - if maxLen >= 0 && numDiffs >= maxLen { - maxGroup = maxGroup.Append(ds) - continue - } - - // Handle equal records. - if ds.NumDiff() == 0 { - // Compute the number of leading and trailing records to print. - var numLo, numHi int - numEqual := ds.NumIgnored + ds.NumIdentical - for numLo < numContextRecords && numLo+numHi < numEqual && i != 0 { - if r := recs[numLo].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { - break - } - numLo++ - } - for numHi < numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { - if r := recs[numEqual-numHi-1].Value; r.NumIgnored > 0 && r.NumSame+r.NumDiff == 0 { - break - } - numHi++ - } - if numEqual-(numLo+numHi) == 1 && ds.NumIgnored == 0 { - numHi++ // Avoid pointless coalescing of a single equal record - } - - // Format the equal values. - for _, r := range recs[:numLo] { - out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs) - list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) - keys = append(keys, r.Key) - } - if numEqual > numLo+numHi { - ds.NumIdentical -= numLo + numHi - list.AppendEllipsis(ds) - for len(keys) < len(list) { - keys = append(keys, reflect.Value{}) - } - } - for _, r := range recs[numEqual-numHi : numEqual] { - out := opts.WithDiffMode(diffIdentical).FormatDiff(r.Value, ptrs) - list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) - keys = append(keys, r.Key) - } - recs = recs[numEqual:] - continue - } - - // Handle unequal records. - for _, r := range recs[:ds.NumDiff()] { - switch { - case opts.CanFormatDiffSlice(r.Value): - out := opts.FormatDiffSlice(r.Value) - list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) - keys = append(keys, r.Key) - case r.Value.NumChildren == r.Value.MaxDepth: - outx := opts.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs) - outy := opts.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs) - for i := 0; i <= maxVerbosityPreset && outx != nil && outy != nil && outx.Equal(outy); i++ { - opts2 := verbosityPreset(opts, i) - outx = opts2.WithDiffMode(diffRemoved).FormatDiff(r.Value, ptrs) - outy = opts2.WithDiffMode(diffInserted).FormatDiff(r.Value, ptrs) - } - if outx != nil { - list = append(list, textRecord{Diff: diffRemoved, Key: formatKey(r.Key), Value: outx}) - keys = append(keys, r.Key) - } - if outy != nil { - list = append(list, textRecord{Diff: diffInserted, Key: formatKey(r.Key), Value: outy}) - keys = append(keys, r.Key) - } - default: - out := opts.FormatDiff(r.Value, ptrs) - list = append(list, textRecord{Key: formatKey(r.Key), Value: out}) - keys = append(keys, r.Key) - } - } - recs = recs[ds.NumDiff():] - numDiffs += ds.NumDiff() - } - if maxGroup.IsZero() { - assert(len(recs) == 0) - } else { - list.AppendEllipsis(maxGroup) - for len(keys) < len(list) { - keys = append(keys, reflect.Value{}) - } - } - assert(len(list) == len(keys)) - - // For maps, the default formatting logic uses fmt.Stringer which may - // produce ambiguous output. Avoid calling String to disambiguate. - if k == reflect.Map { - var ambiguous bool - seenKeys := map[string]reflect.Value{} - for i, currKey := range keys { - if currKey.IsValid() { - strKey := list[i].Key - prevKey, seen := seenKeys[strKey] - if seen && prevKey.CanInterface() && currKey.CanInterface() { - ambiguous = prevKey.Interface() != currKey.Interface() - if ambiguous { - break - } - } - seenKeys[strKey] = currKey - } - } - if ambiguous { - for i, k := range keys { - if k.IsValid() { - list[i].Key = formatMapKey(k, true, ptrs) - } - } - } - } - - return &textWrap{Prefix: "{", Value: list, Suffix: "}"} -} - -// coalesceAdjacentRecords coalesces the list of records into groups of -// adjacent equal, or unequal counts. -func coalesceAdjacentRecords(name string, recs []reportRecord) (groups []diffStats) { - var prevCase int // Arbitrary index into which case last occurred - lastStats := func(i int) *diffStats { - if prevCase != i { - groups = append(groups, diffStats{Name: name}) - prevCase = i - } - return &groups[len(groups)-1] - } - for _, r := range recs { - switch rv := r.Value; { - case rv.NumIgnored > 0 && rv.NumSame+rv.NumDiff == 0: - lastStats(1).NumIgnored++ - case rv.NumDiff == 0: - lastStats(1).NumIdentical++ - case rv.NumDiff > 0 && !rv.ValueY.IsValid(): - lastStats(2).NumRemoved++ - case rv.NumDiff > 0 && !rv.ValueX.IsValid(): - lastStats(2).NumInserted++ - default: - lastStats(2).NumModified++ - } - } - return groups -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/report_references.go b/test/vendor/github.com/google/go-cmp/cmp/report_references.go deleted file mode 100644 index be31b33a9e..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report_references.go +++ /dev/null @@ -1,264 +0,0 @@ -// Copyright 2020, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "fmt" - "reflect" - "strings" - - "github.com/google/go-cmp/cmp/internal/flags" - "github.com/google/go-cmp/cmp/internal/value" -) - -const ( - pointerDelimPrefix = "⟪" - pointerDelimSuffix = "⟫" -) - -// formatPointer prints the address of the pointer. -func formatPointer(p value.Pointer, withDelims bool) string { - v := p.Uintptr() - if flags.Deterministic { - v = 0xdeadf00f // Only used for stable testing purposes - } - if withDelims { - return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix - } - return formatHex(uint64(v)) -} - -// pointerReferences is a stack of pointers visited so far. -type pointerReferences [][2]value.Pointer - -func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) { - if deref && vx.IsValid() { - vx = vx.Addr() - } - if deref && vy.IsValid() { - vy = vy.Addr() - } - switch d { - case diffUnknown, diffIdentical: - pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)} - case diffRemoved: - pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}} - case diffInserted: - pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)} - } - *ps = append(*ps, pp) - return pp -} - -func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) { - p = value.PointerOf(v) - for _, pp := range *ps { - if p == pp[0] || p == pp[1] { - return p, true - } - } - *ps = append(*ps, [2]value.Pointer{p, p}) - return p, false -} - -func (ps *pointerReferences) Pop() { - *ps = (*ps)[:len(*ps)-1] -} - -// trunkReferences is metadata for a textNode indicating that the sub-tree -// represents the value for either pointer in a pair of references. -type trunkReferences struct{ pp [2]value.Pointer } - -// trunkReference is metadata for a textNode indicating that the sub-tree -// represents the value for the given pointer reference. -type trunkReference struct{ p value.Pointer } - -// leafReference is metadata for a textNode indicating that the value is -// truncated as it refers to another part of the tree (i.e., a trunk). -type leafReference struct{ p value.Pointer } - -func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode { - switch { - case pp[0].IsNil(): - return &textWrap{Value: s, Metadata: trunkReference{pp[1]}} - case pp[1].IsNil(): - return &textWrap{Value: s, Metadata: trunkReference{pp[0]}} - case pp[0] == pp[1]: - return &textWrap{Value: s, Metadata: trunkReference{pp[0]}} - default: - return &textWrap{Value: s, Metadata: trunkReferences{pp}} - } -} -func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode { - var prefix string - if printAddress { - prefix = formatPointer(p, true) - } - return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}} -} -func makeLeafReference(p value.Pointer, printAddress bool) textNode { - out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"} - var prefix string - if printAddress { - prefix = formatPointer(p, true) - } - return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}} -} - -// resolveReferences walks the textNode tree searching for any leaf reference -// metadata and resolves each against the corresponding trunk references. -// Since pointer addresses in memory are not particularly readable to the user, -// it replaces each pointer value with an arbitrary and unique reference ID. -func resolveReferences(s textNode) { - var walkNodes func(textNode, func(textNode)) - walkNodes = func(s textNode, f func(textNode)) { - f(s) - switch s := s.(type) { - case *textWrap: - walkNodes(s.Value, f) - case textList: - for _, r := range s { - walkNodes(r.Value, f) - } - } - } - - // Collect all trunks and leaves with reference metadata. - var trunks, leaves []*textWrap - walkNodes(s, func(s textNode) { - if s, ok := s.(*textWrap); ok { - switch s.Metadata.(type) { - case leafReference: - leaves = append(leaves, s) - case trunkReference, trunkReferences: - trunks = append(trunks, s) - } - } - }) - - // No leaf references to resolve. - if len(leaves) == 0 { - return - } - - // Collect the set of all leaf references to resolve. - leafPtrs := make(map[value.Pointer]bool) - for _, leaf := range leaves { - leafPtrs[leaf.Metadata.(leafReference).p] = true - } - - // Collect the set of trunk pointers that are always paired together. - // This allows us to assign a single ID to both pointers for brevity. - // If a pointer in a pair ever occurs by itself or as a different pair, - // then the pair is broken. - pairedTrunkPtrs := make(map[value.Pointer]value.Pointer) - unpair := func(p value.Pointer) { - if !pairedTrunkPtrs[p].IsNil() { - pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half - } - pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half - } - for _, trunk := range trunks { - switch p := trunk.Metadata.(type) { - case trunkReference: - unpair(p.p) // standalone pointer cannot be part of a pair - case trunkReferences: - p0, ok0 := pairedTrunkPtrs[p.pp[0]] - p1, ok1 := pairedTrunkPtrs[p.pp[1]] - switch { - case !ok0 && !ok1: - // Register the newly seen pair. - pairedTrunkPtrs[p.pp[0]] = p.pp[1] - pairedTrunkPtrs[p.pp[1]] = p.pp[0] - case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]: - // Exact pair already seen; do nothing. - default: - // Pair conflicts with some other pair; break all pairs. - unpair(p.pp[0]) - unpair(p.pp[1]) - } - } - } - - // Correlate each pointer referenced by leaves to a unique identifier, - // and print the IDs for each trunk that matches those pointers. - var nextID uint - ptrIDs := make(map[value.Pointer]uint) - newID := func() uint { - id := nextID - nextID++ - return id - } - for _, trunk := range trunks { - switch p := trunk.Metadata.(type) { - case trunkReference: - if print := leafPtrs[p.p]; print { - id, ok := ptrIDs[p.p] - if !ok { - id = newID() - ptrIDs[p.p] = id - } - trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id)) - } - case trunkReferences: - print0 := leafPtrs[p.pp[0]] - print1 := leafPtrs[p.pp[1]] - if print0 || print1 { - id0, ok0 := ptrIDs[p.pp[0]] - id1, ok1 := ptrIDs[p.pp[1]] - isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0] - if isPair { - var id uint - assert(ok0 == ok1) // must be seen together or not at all - if ok0 { - assert(id0 == id1) // must have the same ID - id = id0 - } else { - id = newID() - ptrIDs[p.pp[0]] = id - ptrIDs[p.pp[1]] = id - } - trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id)) - } else { - if print0 && !ok0 { - id0 = newID() - ptrIDs[p.pp[0]] = id0 - } - if print1 && !ok1 { - id1 = newID() - ptrIDs[p.pp[1]] = id1 - } - switch { - case print0 && print1: - trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1)) - case print0: - trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)) - case print1: - trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1)) - } - } - } - } - } - - // Update all leaf references with the unique identifier. - for _, leaf := range leaves { - if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok { - leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id)) - } - } -} - -func formatReference(id uint) string { - return fmt.Sprintf("ref#%d", id) -} - -func updateReferencePrefix(prefix, ref string) string { - if prefix == "" { - return pointerDelimPrefix + ref + pointerDelimSuffix - } - suffix := strings.TrimPrefix(prefix, pointerDelimPrefix) - return pointerDelimPrefix + ref + ": " + suffix -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/test/vendor/github.com/google/go-cmp/cmp/report_reflect.go deleted file mode 100644 index 33f03577f9..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report_reflect.go +++ /dev/null @@ -1,402 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "bytes" - "fmt" - "reflect" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "github.com/google/go-cmp/cmp/internal/value" -) - -type formatValueOptions struct { - // AvoidStringer controls whether to avoid calling custom stringer - // methods like error.Error or fmt.Stringer.String. - AvoidStringer bool - - // PrintAddresses controls whether to print the address of all pointers, - // slice elements, and maps. - PrintAddresses bool - - // QualifiedNames controls whether FormatType uses the fully qualified name - // (including the full package path as opposed to just the package name). - QualifiedNames bool - - // VerbosityLevel controls the amount of output to produce. - // A higher value produces more output. A value of zero or lower produces - // no output (represented using an ellipsis). - // If LimitVerbosity is false, then the level is treated as infinite. - VerbosityLevel int - - // LimitVerbosity specifies that formatting should respect VerbosityLevel. - LimitVerbosity bool -} - -// FormatType prints the type as if it were wrapping s. -// This may return s as-is depending on the current type and TypeMode mode. -func (opts formatOptions) FormatType(t reflect.Type, s textNode) textNode { - // Check whether to emit the type or not. - switch opts.TypeMode { - case autoType: - switch t.Kind() { - case reflect.Struct, reflect.Slice, reflect.Array, reflect.Map: - if s.Equal(textNil) { - return s - } - default: - return s - } - if opts.DiffMode == diffIdentical { - return s // elide type for identical nodes - } - case elideType: - return s - } - - // Determine the type label, applying special handling for unnamed types. - typeName := value.TypeString(t, opts.QualifiedNames) - if t.Name() == "" { - // According to Go grammar, certain type literals contain symbols that - // do not strongly bind to the next lexicographical token (e.g., *T). - switch t.Kind() { - case reflect.Chan, reflect.Func, reflect.Ptr: - typeName = "(" + typeName + ")" - } - } - return &textWrap{Prefix: typeName, Value: wrapParens(s)} -} - -// wrapParens wraps s with a set of parenthesis, but avoids it if the -// wrapped node itself is already surrounded by a pair of parenthesis or braces. -// It handles unwrapping one level of pointer-reference nodes. -func wrapParens(s textNode) textNode { - var refNode *textWrap - if s2, ok := s.(*textWrap); ok { - // Unwrap a single pointer reference node. - switch s2.Metadata.(type) { - case leafReference, trunkReference, trunkReferences: - refNode = s2 - if s3, ok := refNode.Value.(*textWrap); ok { - s2 = s3 - } - } - - // Already has delimiters that make parenthesis unnecessary. - hasParens := strings.HasPrefix(s2.Prefix, "(") && strings.HasSuffix(s2.Suffix, ")") - hasBraces := strings.HasPrefix(s2.Prefix, "{") && strings.HasSuffix(s2.Suffix, "}") - if hasParens || hasBraces { - return s - } - } - if refNode != nil { - refNode.Value = &textWrap{Prefix: "(", Value: refNode.Value, Suffix: ")"} - return s - } - return &textWrap{Prefix: "(", Value: s, Suffix: ")"} -} - -// FormatValue prints the reflect.Value, taking extra care to avoid descending -// into pointers already in ptrs. As pointers are visited, ptrs is also updated. -func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, ptrs *pointerReferences) (out textNode) { - if !v.IsValid() { - return nil - } - t := v.Type() - - // Check slice element for cycles. - if parentKind == reflect.Slice { - ptrRef, visited := ptrs.Push(v.Addr()) - if visited { - return makeLeafReference(ptrRef, false) - } - defer ptrs.Pop() - defer func() { out = wrapTrunkReference(ptrRef, false, out) }() - } - - // Check whether there is an Error or String method to call. - if !opts.AvoidStringer && v.CanInterface() { - // Avoid calling Error or String methods on nil receivers since many - // implementations crash when doing so. - if (t.Kind() != reflect.Ptr && t.Kind() != reflect.Interface) || !v.IsNil() { - var prefix, strVal string - func() { - // Swallow and ignore any panics from String or Error. - defer func() { recover() }() - switch v := v.Interface().(type) { - case error: - strVal = v.Error() - prefix = "e" - case fmt.Stringer: - strVal = v.String() - prefix = "s" - } - }() - if prefix != "" { - return opts.formatString(prefix, strVal) - } - } - } - - // Check whether to explicitly wrap the result with the type. - var skipType bool - defer func() { - if !skipType { - out = opts.FormatType(t, out) - } - }() - - switch t.Kind() { - case reflect.Bool: - return textLine(fmt.Sprint(v.Bool())) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return textLine(fmt.Sprint(v.Int())) - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return textLine(fmt.Sprint(v.Uint())) - case reflect.Uint8: - if parentKind == reflect.Slice || parentKind == reflect.Array { - return textLine(formatHex(v.Uint())) - } - return textLine(fmt.Sprint(v.Uint())) - case reflect.Uintptr: - return textLine(formatHex(v.Uint())) - case reflect.Float32, reflect.Float64: - return textLine(fmt.Sprint(v.Float())) - case reflect.Complex64, reflect.Complex128: - return textLine(fmt.Sprint(v.Complex())) - case reflect.String: - return opts.formatString("", v.String()) - case reflect.UnsafePointer, reflect.Chan, reflect.Func: - return textLine(formatPointer(value.PointerOf(v), true)) - case reflect.Struct: - var list textList - v := makeAddressable(v) // needed for retrieveUnexportedField - maxLen := v.NumField() - if opts.LimitVerbosity { - maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... - opts.VerbosityLevel-- - } - for i := 0; i < v.NumField(); i++ { - vv := v.Field(i) - if value.IsZero(vv) { - continue // Elide fields with zero values - } - if len(list) == maxLen { - list.AppendEllipsis(diffStats{}) - break - } - sf := t.Field(i) - if supportExporters && !isExported(sf.Name) { - vv = retrieveUnexportedField(v, sf, true) - } - s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs) - list = append(list, textRecord{Key: sf.Name, Value: s}) - } - return &textWrap{Prefix: "{", Value: list, Suffix: "}"} - case reflect.Slice: - if v.IsNil() { - return textNil - } - - // Check whether this is a []byte of text data. - if t.Elem() == reflect.TypeOf(byte(0)) { - b := v.Bytes() - isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) } - if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { - out = opts.formatString("", string(b)) - return opts.WithTypeMode(emitType).FormatType(t, out) - } - } - - fallthrough - case reflect.Array: - maxLen := v.Len() - if opts.LimitVerbosity { - maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... - opts.VerbosityLevel-- - } - var list textList - for i := 0; i < v.Len(); i++ { - if len(list) == maxLen { - list.AppendEllipsis(diffStats{}) - break - } - s := opts.WithTypeMode(elideType).FormatValue(v.Index(i), t.Kind(), ptrs) - list = append(list, textRecord{Value: s}) - } - - out = &textWrap{Prefix: "{", Value: list, Suffix: "}"} - if t.Kind() == reflect.Slice && opts.PrintAddresses { - header := fmt.Sprintf("ptr:%v, len:%d, cap:%d", formatPointer(value.PointerOf(v), false), v.Len(), v.Cap()) - out = &textWrap{Prefix: pointerDelimPrefix + header + pointerDelimSuffix, Value: out} - } - return out - case reflect.Map: - if v.IsNil() { - return textNil - } - - // Check pointer for cycles. - ptrRef, visited := ptrs.Push(v) - if visited { - return makeLeafReference(ptrRef, opts.PrintAddresses) - } - defer ptrs.Pop() - - maxLen := v.Len() - if opts.LimitVerbosity { - maxLen = ((1 << opts.verbosity()) >> 1) << 2 // 0, 4, 8, 16, 32, etc... - opts.VerbosityLevel-- - } - var list textList - for _, k := range value.SortKeys(v.MapKeys()) { - if len(list) == maxLen { - list.AppendEllipsis(diffStats{}) - break - } - sk := formatMapKey(k, false, ptrs) - sv := opts.WithTypeMode(elideType).FormatValue(v.MapIndex(k), t.Kind(), ptrs) - list = append(list, textRecord{Key: sk, Value: sv}) - } - - out = &textWrap{Prefix: "{", Value: list, Suffix: "}"} - out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) - return out - case reflect.Ptr: - if v.IsNil() { - return textNil - } - - // Check pointer for cycles. - ptrRef, visited := ptrs.Push(v) - if visited { - out = makeLeafReference(ptrRef, opts.PrintAddresses) - return &textWrap{Prefix: "&", Value: out} - } - defer ptrs.Pop() - - skipType = true // Let the underlying value print the type instead - out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) - out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) - out = &textWrap{Prefix: "&", Value: out} - return out - case reflect.Interface: - if v.IsNil() { - return textNil - } - // Interfaces accept different concrete types, - // so configure the underlying value to explicitly print the type. - skipType = true // Print the concrete type instead - return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs) - default: - panic(fmt.Sprintf("%v kind not handled", v.Kind())) - } -} - -func (opts formatOptions) formatString(prefix, s string) textNode { - maxLen := len(s) - maxLines := strings.Count(s, "\n") + 1 - if opts.LimitVerbosity { - maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... - maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc... - } - - // For multiline strings, use the triple-quote syntax, - // but only use it when printing removed or inserted nodes since - // we only want the extra verbosity for those cases. - lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n") - isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+') - for i := 0; i < len(lines) && isTripleQuoted; i++ { - lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support - isPrintable := func(r rune) bool { - return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable - } - line := lines[i] - isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen - } - if isTripleQuoted { - var list textList - list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true}) - for i, line := range lines { - if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 { - comment := commentString(fmt.Sprintf("%d elided lines", numElided)) - list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment}) - break - } - list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true}) - } - list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true}) - return &textWrap{Prefix: "(", Value: list, Suffix: ")"} - } - - // Format the string as a single-line quoted string. - if len(s) > maxLen+len(textEllipsis) { - return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis)) - } - return textLine(prefix + formatString(s)) -} - -// formatMapKey formats v as if it were a map key. -// The result is guaranteed to be a single line. -func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string { - var opts formatOptions - opts.DiffMode = diffIdentical - opts.TypeMode = elideType - opts.PrintAddresses = disambiguate - opts.AvoidStringer = disambiguate - opts.QualifiedNames = disambiguate - opts.VerbosityLevel = maxVerbosityPreset - opts.LimitVerbosity = true - s := opts.FormatValue(v, reflect.Map, ptrs).String() - return strings.TrimSpace(s) -} - -// formatString prints s as a double-quoted or backtick-quoted string. -func formatString(s string) string { - // Use quoted string if it the same length as a raw string literal. - // Otherwise, attempt to use the raw string form. - qs := strconv.Quote(s) - if len(qs) == 1+len(s)+1 { - return qs - } - - // Disallow newlines to ensure output is a single line. - // Only allow printable runes for readability purposes. - rawInvalid := func(r rune) bool { - return r == '`' || r == '\n' || !(unicode.IsPrint(r) || r == '\t') - } - if utf8.ValidString(s) && strings.IndexFunc(s, rawInvalid) < 0 { - return "`" + s + "`" - } - return qs -} - -// formatHex prints u as a hexadecimal integer in Go notation. -func formatHex(u uint64) string { - var f string - switch { - case u <= 0xff: - f = "0x%02x" - case u <= 0xffff: - f = "0x%04x" - case u <= 0xffffff: - f = "0x%06x" - case u <= 0xffffffff: - f = "0x%08x" - case u <= 0xffffffffff: - f = "0x%010x" - case u <= 0xffffffffffff: - f = "0x%012x" - case u <= 0xffffffffffffff: - f = "0x%014x" - case u <= 0xffffffffffffffff: - f = "0x%016x" - } - return fmt.Sprintf(f, u) -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/report_slices.go b/test/vendor/github.com/google/go-cmp/cmp/report_slices.go deleted file mode 100644 index 2ad3bc85ba..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report_slices.go +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "bytes" - "fmt" - "math" - "reflect" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "github.com/google/go-cmp/cmp/internal/diff" -) - -// CanFormatDiffSlice reports whether we support custom formatting for nodes -// that are slices of primitive kinds or strings. -func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { - switch { - case opts.DiffMode != diffUnknown: - return false // Must be formatting in diff mode - case v.NumDiff == 0: - return false // No differences detected - case !v.ValueX.IsValid() || !v.ValueY.IsValid(): - return false // Both values must be valid - case v.NumIgnored > 0: - return false // Some ignore option was used - case v.NumTransformed > 0: - return false // Some transform option was used - case v.NumCompared > 1: - return false // More than one comparison was used - case v.NumCompared == 1 && v.Type.Name() != "": - // The need for cmp to check applicability of options on every element - // in a slice is a significant performance detriment for large []byte. - // The workaround is to specify Comparer(bytes.Equal), - // which enables cmp to compare []byte more efficiently. - // If they differ, we still want to provide batched diffing. - // The logic disallows named types since they tend to have their own - // String method, with nicer formatting than what this provides. - return false - } - - // Check whether this is an interface with the same concrete types. - t := v.Type - vx, vy := v.ValueX, v.ValueY - if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() { - vx, vy = vx.Elem(), vy.Elem() - t = vx.Type() - } - - // Check whether we provide specialized diffing for this type. - switch t.Kind() { - case reflect.String: - case reflect.Array, reflect.Slice: - // Only slices of primitive types have specialized handling. - switch t.Elem().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, - reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: - default: - return false - } - - // Both slice values have to be non-empty. - if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) { - return false - } - - // If a sufficient number of elements already differ, - // use specialized formatting even if length requirement is not met. - if v.NumDiff > v.NumSame { - return true - } - default: - return false - } - - // Use specialized string diffing for longer slices or strings. - const minLength = 64 - return vx.Len() >= minLength && vy.Len() >= minLength -} - -// FormatDiffSlice prints a diff for the slices (or strings) represented by v. -// This provides custom-tailored logic to make printing of differences in -// textual strings and slices of primitive kinds more readable. -func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { - assert(opts.DiffMode == diffUnknown) - t, vx, vy := v.Type, v.ValueX, v.ValueY - if t.Kind() == reflect.Interface { - vx, vy = vx.Elem(), vy.Elem() - t = vx.Type() - opts = opts.WithTypeMode(emitType) - } - - // Auto-detect the type of the data. - var sx, sy string - var ssx, ssy []string - var isString, isMostlyText, isPureLinedText, isBinary bool - switch { - case t.Kind() == reflect.String: - sx, sy = vx.String(), vy.String() - isString = true - case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): - sx, sy = string(vx.Bytes()), string(vy.Bytes()) - isString = true - case t.Kind() == reflect.Array: - // Arrays need to be addressable for slice operations to work. - vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() - vx2.Set(vx) - vy2.Set(vy) - vx, vy = vx2, vy2 - } - if isString { - var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int - for i, r := range sx + sy { - numTotalRunes++ - if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError { - numValidRunes++ - } - if r == '\n' { - if maxLineLen < i-lastLineIdx { - maxLineLen = i - lastLineIdx - } - lastLineIdx = i + 1 - numLines++ - } - } - isPureText := numValidRunes == numTotalRunes - isMostlyText = float64(numValidRunes) > math.Floor(0.90*float64(numTotalRunes)) - isPureLinedText = isPureText && numLines >= 4 && maxLineLen <= 1024 - isBinary = !isMostlyText - - // Avoid diffing by lines if it produces a significantly more complex - // edit script than diffing by bytes. - if isPureLinedText { - ssx = strings.Split(sx, "\n") - ssy = strings.Split(sy, "\n") - esLines := diff.Difference(len(ssx), len(ssy), func(ix, iy int) diff.Result { - return diff.BoolResult(ssx[ix] == ssy[iy]) - }) - esBytes := diff.Difference(len(sx), len(sy), func(ix, iy int) diff.Result { - return diff.BoolResult(sx[ix] == sy[iy]) - }) - efficiencyLines := float64(esLines.Dist()) / float64(len(esLines)) - efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes)) - isPureLinedText = efficiencyLines < 4*efficiencyBytes - } - } - - // Format the string into printable records. - var list textList - var delim string - switch { - // If the text appears to be multi-lined text, - // then perform differencing across individual lines. - case isPureLinedText: - list = opts.formatDiffSlice( - reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", - func(v reflect.Value, d diffMode) textRecord { - s := formatString(v.Index(0).String()) - return textRecord{Diff: d, Value: textLine(s)} - }, - ) - delim = "\n" - - // If possible, use a custom triple-quote (""") syntax for printing - // differences in a string literal. This format is more readable, - // but has edge-cases where differences are visually indistinguishable. - // This format is avoided under the following conditions: - // • A line starts with `"""` - // • A line starts with "..." - // • A line contains non-printable characters - // • Adjacent different lines differ only by whitespace - // - // For example: - // """ - // ... // 3 identical lines - // foo - // bar - // - baz - // + BAZ - // """ - isTripleQuoted := true - prevRemoveLines := map[string]bool{} - prevInsertLines := map[string]bool{} - var list2 textList - list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true}) - for _, r := range list { - if !r.Value.Equal(textEllipsis) { - line, _ := strconv.Unquote(string(r.Value.(textLine))) - line = strings.TrimPrefix(strings.TrimSuffix(line, "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support - normLine := strings.Map(func(r rune) rune { - if unicode.IsSpace(r) { - return -1 // drop whitespace to avoid visually indistinguishable output - } - return r - }, line) - isPrintable := func(r rune) bool { - return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable - } - isTripleQuoted = !strings.HasPrefix(line, `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" - switch r.Diff { - case diffRemoved: - isTripleQuoted = isTripleQuoted && !prevInsertLines[normLine] - prevRemoveLines[normLine] = true - case diffInserted: - isTripleQuoted = isTripleQuoted && !prevRemoveLines[normLine] - prevInsertLines[normLine] = true - } - if !isTripleQuoted { - break - } - r.Value = textLine(line) - r.ElideComma = true - } - if !(r.Diff == diffRemoved || r.Diff == diffInserted) { // start a new non-adjacent difference group - prevRemoveLines = map[string]bool{} - prevInsertLines = map[string]bool{} - } - list2 = append(list2, r) - } - if r := list2[len(list2)-1]; r.Diff == diffIdentical && len(r.Value.(textLine)) == 0 { - list2 = list2[:len(list2)-1] // elide single empty line at the end - } - list2 = append(list2, textRecord{Value: textLine(`"""`), ElideComma: true}) - if isTripleQuoted { - var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} - switch t.Kind() { - case reflect.String: - if t != reflect.TypeOf(string("")) { - out = opts.FormatType(t, out) - } - case reflect.Slice: - // Always emit type for slices since the triple-quote syntax - // looks like a string (not a slice). - opts = opts.WithTypeMode(emitType) - out = opts.FormatType(t, out) - } - return out - } - - // If the text appears to be single-lined text, - // then perform differencing in approximately fixed-sized chunks. - // The output is printed as quoted strings. - case isMostlyText: - list = opts.formatDiffSlice( - reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", - func(v reflect.Value, d diffMode) textRecord { - s := formatString(v.String()) - return textRecord{Diff: d, Value: textLine(s)} - }, - ) - - // If the text appears to be binary data, - // then perform differencing in approximately fixed-sized chunks. - // The output is inspired by hexdump. - case isBinary: - list = opts.formatDiffSlice( - reflect.ValueOf(sx), reflect.ValueOf(sy), 16, "byte", - func(v reflect.Value, d diffMode) textRecord { - var ss []string - for i := 0; i < v.Len(); i++ { - ss = append(ss, formatHex(v.Index(i).Uint())) - } - s := strings.Join(ss, ", ") - comment := commentString(fmt.Sprintf("%c|%v|", d, formatASCII(v.String()))) - return textRecord{Diff: d, Value: textLine(s), Comment: comment} - }, - ) - - // For all other slices of primitive types, - // then perform differencing in approximately fixed-sized chunks. - // The size of each chunk depends on the width of the element kind. - default: - var chunkSize int - if t.Elem().Kind() == reflect.Bool { - chunkSize = 16 - } else { - switch t.Elem().Bits() { - case 8: - chunkSize = 16 - case 16: - chunkSize = 12 - case 32: - chunkSize = 8 - default: - chunkSize = 8 - } - } - list = opts.formatDiffSlice( - vx, vy, chunkSize, t.Elem().Kind().String(), - func(v reflect.Value, d diffMode) textRecord { - var ss []string - for i := 0; i < v.Len(); i++ { - switch t.Elem().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - ss = append(ss, fmt.Sprint(v.Index(i).Int())) - case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - ss = append(ss, fmt.Sprint(v.Index(i).Uint())) - case reflect.Uint8, reflect.Uintptr: - ss = append(ss, formatHex(v.Index(i).Uint())) - case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: - ss = append(ss, fmt.Sprint(v.Index(i).Interface())) - } - } - s := strings.Join(ss, ", ") - return textRecord{Diff: d, Value: textLine(s)} - }, - ) - } - - // Wrap the output with appropriate type information. - var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"} - if !isMostlyText { - // The "{...}" byte-sequence literal is not valid Go syntax for strings. - // Emit the type for extra clarity (e.g. "string{...}"). - if t.Kind() == reflect.String { - opts = opts.WithTypeMode(emitType) - } - return opts.FormatType(t, out) - } - switch t.Kind() { - case reflect.String: - out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} - if t != reflect.TypeOf(string("")) { - out = opts.FormatType(t, out) - } - case reflect.Slice: - out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} - if t != reflect.TypeOf([]byte(nil)) { - out = opts.FormatType(t, out) - } - } - return out -} - -// formatASCII formats s as an ASCII string. -// This is useful for printing binary strings in a semi-legible way. -func formatASCII(s string) string { - b := bytes.Repeat([]byte{'.'}, len(s)) - for i := 0; i < len(s); i++ { - if ' ' <= s[i] && s[i] <= '~' { - b[i] = s[i] - } - } - return string(b) -} - -func (opts formatOptions) formatDiffSlice( - vx, vy reflect.Value, chunkSize int, name string, - makeRec func(reflect.Value, diffMode) textRecord, -) (list textList) { - eq := func(ix, iy int) bool { - return vx.Index(ix).Interface() == vy.Index(iy).Interface() - } - es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result { - return diff.BoolResult(eq(ix, iy)) - }) - - appendChunks := func(v reflect.Value, d diffMode) int { - n0 := v.Len() - for v.Len() > 0 { - n := chunkSize - if n > v.Len() { - n = v.Len() - } - list = append(list, makeRec(v.Slice(0, n), d)) - v = v.Slice(n, v.Len()) - } - return n0 - v.Len() - } - - var numDiffs int - maxLen := -1 - if opts.LimitVerbosity { - maxLen = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc... - opts.VerbosityLevel-- - } - - groups := coalesceAdjacentEdits(name, es) - groups = coalesceInterveningIdentical(groups, chunkSize/4) - groups = cleanupSurroundingIdentical(groups, eq) - maxGroup := diffStats{Name: name} - for i, ds := range groups { - if maxLen >= 0 && numDiffs >= maxLen { - maxGroup = maxGroup.Append(ds) - continue - } - - // Print equal. - if ds.NumDiff() == 0 { - // Compute the number of leading and trailing equal bytes to print. - var numLo, numHi int - numEqual := ds.NumIgnored + ds.NumIdentical - for numLo < chunkSize*numContextRecords && numLo+numHi < numEqual && i != 0 { - numLo++ - } - for numHi < chunkSize*numContextRecords && numLo+numHi < numEqual && i != len(groups)-1 { - numHi++ - } - if numEqual-(numLo+numHi) <= chunkSize && ds.NumIgnored == 0 { - numHi = numEqual - numLo // Avoid pointless coalescing of single equal row - } - - // Print the equal bytes. - appendChunks(vx.Slice(0, numLo), diffIdentical) - if numEqual > numLo+numHi { - ds.NumIdentical -= numLo + numHi - list.AppendEllipsis(ds) - } - appendChunks(vx.Slice(numEqual-numHi, numEqual), diffIdentical) - vx = vx.Slice(numEqual, vx.Len()) - vy = vy.Slice(numEqual, vy.Len()) - continue - } - - // Print unequal. - len0 := len(list) - nx := appendChunks(vx.Slice(0, ds.NumIdentical+ds.NumRemoved+ds.NumModified), diffRemoved) - vx = vx.Slice(nx, vx.Len()) - ny := appendChunks(vy.Slice(0, ds.NumIdentical+ds.NumInserted+ds.NumModified), diffInserted) - vy = vy.Slice(ny, vy.Len()) - numDiffs += len(list) - len0 - } - if maxGroup.IsZero() { - assert(vx.Len() == 0 && vy.Len() == 0) - } else { - list.AppendEllipsis(maxGroup) - } - return list -} - -// coalesceAdjacentEdits coalesces the list of edits into groups of adjacent -// equal or unequal counts. -// -// Example: -// -// Input: "..XXY...Y" -// Output: [ -// {NumIdentical: 2}, -// {NumRemoved: 2, NumInserted 1}, -// {NumIdentical: 3}, -// {NumInserted: 1}, -// ] -// -func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { - var prevMode byte - lastStats := func(mode byte) *diffStats { - if prevMode != mode { - groups = append(groups, diffStats{Name: name}) - prevMode = mode - } - return &groups[len(groups)-1] - } - for _, e := range es { - switch e { - case diff.Identity: - lastStats('=').NumIdentical++ - case diff.UniqueX: - lastStats('!').NumRemoved++ - case diff.UniqueY: - lastStats('!').NumInserted++ - case diff.Modified: - lastStats('!').NumModified++ - } - } - return groups -} - -// coalesceInterveningIdentical coalesces sufficiently short (<= windowSize) -// equal groups into adjacent unequal groups that currently result in a -// dual inserted/removed printout. This acts as a high-pass filter to smooth -// out high-frequency changes within the windowSize. -// -// Example: -// -// WindowSize: 16, -// Input: [ -// {NumIdentical: 61}, // group 0 -// {NumRemoved: 3, NumInserted: 1}, // group 1 -// {NumIdentical: 6}, // ├── coalesce -// {NumInserted: 2}, // ├── coalesce -// {NumIdentical: 1}, // ├── coalesce -// {NumRemoved: 9}, // └── coalesce -// {NumIdentical: 64}, // group 2 -// {NumRemoved: 3, NumInserted: 1}, // group 3 -// {NumIdentical: 6}, // ├── coalesce -// {NumInserted: 2}, // ├── coalesce -// {NumIdentical: 1}, // ├── coalesce -// {NumRemoved: 7}, // ├── coalesce -// {NumIdentical: 1}, // ├── coalesce -// {NumRemoved: 2}, // └── coalesce -// {NumIdentical: 63}, // group 4 -// ] -// Output: [ -// {NumIdentical: 61}, -// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, -// {NumIdentical: 64}, -// {NumIdentical: 8, NumRemoved: 12, NumInserted: 3}, -// {NumIdentical: 63}, -// ] -// -func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { - groups, groupsOrig := groups[:0], groups - for i, ds := range groupsOrig { - if len(groups) >= 2 && ds.NumDiff() > 0 { - prev := &groups[len(groups)-2] // Unequal group - curr := &groups[len(groups)-1] // Equal group - next := &groupsOrig[i] // Unequal group - hadX, hadY := prev.NumRemoved > 0, prev.NumInserted > 0 - hasX, hasY := next.NumRemoved > 0, next.NumInserted > 0 - if ((hadX || hasX) && (hadY || hasY)) && curr.NumIdentical <= windowSize { - *prev = prev.Append(*curr).Append(*next) - groups = groups[:len(groups)-1] // Truncate off equal group - continue - } - } - groups = append(groups, ds) - } - return groups -} - -// cleanupSurroundingIdentical scans through all unequal groups, and -// moves any leading sequence of equal elements to the preceding equal group and -// moves and trailing sequence of equal elements to the succeeding equal group. -// -// This is necessary since coalesceInterveningIdentical may coalesce edit groups -// together such that leading/trailing spans of equal elements becomes possible. -// Note that this can occur even with an optimal diffing algorithm. -// -// Example: -// -// Input: [ -// {NumIdentical: 61}, -// {NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements -// {NumIdentical: 67}, -// {NumIdentical: 7, NumRemoved: 12, NumInserted: 3}, // assume 10 trailing identical elements -// {NumIdentical: 54}, -// ] -// Output: [ -// {NumIdentical: 64}, // incremented by 3 -// {NumRemoved: 9}, -// {NumIdentical: 67}, -// {NumRemoved: 9}, -// {NumIdentical: 64}, // incremented by 10 -// ] -// -func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats { - var ix, iy int // indexes into sequence x and y - for i, ds := range groups { - // Handle equal group. - if ds.NumDiff() == 0 { - ix += ds.NumIdentical - iy += ds.NumIdentical - continue - } - - // Handle unequal group. - nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified - ny := ds.NumIdentical + ds.NumInserted + ds.NumModified - var numLeadingIdentical, numTrailingIdentical int - for i := 0; i < nx && i < ny && eq(ix+i, iy+i); i++ { - numLeadingIdentical++ - } - for i := 0; i < nx && i < ny && eq(ix+nx-1-i, iy+ny-1-i); i++ { - numTrailingIdentical++ - } - if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 { - if numLeadingIdentical > 0 { - // Remove leading identical span from this group and - // insert it into the preceding group. - if i-1 >= 0 { - groups[i-1].NumIdentical += numLeadingIdentical - } else { - // No preceding group exists, so prepend a new group, - // but do so after we finish iterating over all groups. - defer func() { - groups = append([]diffStats{{Name: groups[0].Name, NumIdentical: numLeadingIdentical}}, groups...) - }() - } - // Increment indexes since the preceding group would have handled this. - ix += numLeadingIdentical - iy += numLeadingIdentical - } - if numTrailingIdentical > 0 { - // Remove trailing identical span from this group and - // insert it into the succeeding group. - if i+1 < len(groups) { - groups[i+1].NumIdentical += numTrailingIdentical - } else { - // No succeeding group exists, so append a new group, - // but do so after we finish iterating over all groups. - defer func() { - groups = append(groups, diffStats{Name: groups[len(groups)-1].Name, NumIdentical: numTrailingIdentical}) - }() - } - // Do not increment indexes since the succeeding group will handle this. - } - - // Update this group since some identical elements were removed. - nx -= numIdentical - ny -= numIdentical - groups[i] = diffStats{Name: ds.Name, NumRemoved: nx, NumInserted: ny} - } - ix += nx - iy += ny - } - return groups -} diff --git a/test/vendor/github.com/google/go-cmp/cmp/report_text.go b/test/vendor/github.com/google/go-cmp/cmp/report_text.go deleted file mode 100644 index 0fd46d7ffb..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report_text.go +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import ( - "bytes" - "fmt" - "math/rand" - "strings" - "time" - "unicode/utf8" - - "github.com/google/go-cmp/cmp/internal/flags" -) - -var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 - -const maxColumnLength = 80 - -type indentMode int - -func (n indentMode) appendIndent(b []byte, d diffMode) []byte { - // The output of Diff is documented as being unstable to provide future - // flexibility in changing the output for more humanly readable reports. - // This logic intentionally introduces instability to the exact output - // so that users can detect accidental reliance on stability early on, - // rather than much later when an actual change to the format occurs. - if flags.Deterministic || randBool { - // Use regular spaces (U+0020). - switch d { - case diffUnknown, diffIdentical: - b = append(b, " "...) - case diffRemoved: - b = append(b, "- "...) - case diffInserted: - b = append(b, "+ "...) - } - } else { - // Use non-breaking spaces (U+00a0). - switch d { - case diffUnknown, diffIdentical: - b = append(b, "  "...) - case diffRemoved: - b = append(b, "- "...) - case diffInserted: - b = append(b, "+ "...) - } - } - return repeatCount(n).appendChar(b, '\t') -} - -type repeatCount int - -func (n repeatCount) appendChar(b []byte, c byte) []byte { - for ; n > 0; n-- { - b = append(b, c) - } - return b -} - -// textNode is a simplified tree-based representation of structured text. -// Possible node types are textWrap, textList, or textLine. -type textNode interface { - // Len reports the length in bytes of a single-line version of the tree. - // Nested textRecord.Diff and textRecord.Comment fields are ignored. - Len() int - // Equal reports whether the two trees are structurally identical. - // Nested textRecord.Diff and textRecord.Comment fields are compared. - Equal(textNode) bool - // String returns the string representation of the text tree. - // It is not guaranteed that len(x.String()) == x.Len(), - // nor that x.String() == y.String() implies that x.Equal(y). - String() string - - // formatCompactTo formats the contents of the tree as a single-line string - // to the provided buffer. Any nested textRecord.Diff and textRecord.Comment - // fields are ignored. - // - // However, not all nodes in the tree should be collapsed as a single-line. - // If a node can be collapsed as a single-line, it is replaced by a textLine - // node. Since the top-level node cannot replace itself, this also returns - // the current node itself. - // - // This does not mutate the receiver. - formatCompactTo([]byte, diffMode) ([]byte, textNode) - // formatExpandedTo formats the contents of the tree as a multi-line string - // to the provided buffer. In order for column alignment to operate well, - // formatCompactTo must be called before calling formatExpandedTo. - formatExpandedTo([]byte, diffMode, indentMode) []byte -} - -// textWrap is a wrapper that concatenates a prefix and/or a suffix -// to the underlying node. -type textWrap struct { - Prefix string // e.g., "bytes.Buffer{" - Value textNode // textWrap | textList | textLine - Suffix string // e.g., "}" - Metadata interface{} // arbitrary metadata; has no effect on formatting -} - -func (s *textWrap) Len() int { - return len(s.Prefix) + s.Value.Len() + len(s.Suffix) -} -func (s1 *textWrap) Equal(s2 textNode) bool { - if s2, ok := s2.(*textWrap); ok { - return s1.Prefix == s2.Prefix && s1.Value.Equal(s2.Value) && s1.Suffix == s2.Suffix - } - return false -} -func (s *textWrap) String() string { - var d diffMode - var n indentMode - _, s2 := s.formatCompactTo(nil, d) - b := n.appendIndent(nil, d) // Leading indent - b = s2.formatExpandedTo(b, d, n) // Main body - b = append(b, '\n') // Trailing newline - return string(b) -} -func (s *textWrap) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { - n0 := len(b) // Original buffer length - b = append(b, s.Prefix...) - b, s.Value = s.Value.formatCompactTo(b, d) - b = append(b, s.Suffix...) - if _, ok := s.Value.(textLine); ok { - return b, textLine(b[n0:]) - } - return b, s -} -func (s *textWrap) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte { - b = append(b, s.Prefix...) - b = s.Value.formatExpandedTo(b, d, n) - b = append(b, s.Suffix...) - return b -} - -// textList is a comma-separated list of textWrap or textLine nodes. -// The list may be formatted as multi-lines or single-line at the discretion -// of the textList.formatCompactTo method. -type textList []textRecord -type textRecord struct { - Diff diffMode // e.g., 0 or '-' or '+' - Key string // e.g., "MyField" - Value textNode // textWrap | textLine - ElideComma bool // avoid trailing comma - Comment fmt.Stringer // e.g., "6 identical fields" -} - -// AppendEllipsis appends a new ellipsis node to the list if none already -// exists at the end. If cs is non-zero it coalesces the statistics with the -// previous diffStats. -func (s *textList) AppendEllipsis(ds diffStats) { - hasStats := !ds.IsZero() - if len(*s) == 0 || !(*s)[len(*s)-1].Value.Equal(textEllipsis) { - if hasStats { - *s = append(*s, textRecord{Value: textEllipsis, ElideComma: true, Comment: ds}) - } else { - *s = append(*s, textRecord{Value: textEllipsis, ElideComma: true}) - } - return - } - if hasStats { - (*s)[len(*s)-1].Comment = (*s)[len(*s)-1].Comment.(diffStats).Append(ds) - } -} - -func (s textList) Len() (n int) { - for i, r := range s { - n += len(r.Key) - if r.Key != "" { - n += len(": ") - } - n += r.Value.Len() - if i < len(s)-1 { - n += len(", ") - } - } - return n -} - -func (s1 textList) Equal(s2 textNode) bool { - if s2, ok := s2.(textList); ok { - if len(s1) != len(s2) { - return false - } - for i := range s1 { - r1, r2 := s1[i], s2[i] - if !(r1.Diff == r2.Diff && r1.Key == r2.Key && r1.Value.Equal(r2.Value) && r1.Comment == r2.Comment) { - return false - } - } - return true - } - return false -} - -func (s textList) String() string { - return (&textWrap{Prefix: "{", Value: s, Suffix: "}"}).String() -} - -func (s textList) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { - s = append(textList(nil), s...) // Avoid mutating original - - // Determine whether we can collapse this list as a single line. - n0 := len(b) // Original buffer length - var multiLine bool - for i, r := range s { - if r.Diff == diffInserted || r.Diff == diffRemoved { - multiLine = true - } - b = append(b, r.Key...) - if r.Key != "" { - b = append(b, ": "...) - } - b, s[i].Value = r.Value.formatCompactTo(b, d|r.Diff) - if _, ok := s[i].Value.(textLine); !ok { - multiLine = true - } - if r.Comment != nil { - multiLine = true - } - if i < len(s)-1 { - b = append(b, ", "...) - } - } - // Force multi-lined output when printing a removed/inserted node that - // is sufficiently long. - if (d == diffInserted || d == diffRemoved) && len(b[n0:]) > maxColumnLength { - multiLine = true - } - if !multiLine { - return b, textLine(b[n0:]) - } - return b, s -} - -func (s textList) formatExpandedTo(b []byte, d diffMode, n indentMode) []byte { - alignKeyLens := s.alignLens( - func(r textRecord) bool { - _, isLine := r.Value.(textLine) - return r.Key == "" || !isLine - }, - func(r textRecord) int { return utf8.RuneCountInString(r.Key) }, - ) - alignValueLens := s.alignLens( - func(r textRecord) bool { - _, isLine := r.Value.(textLine) - return !isLine || r.Value.Equal(textEllipsis) || r.Comment == nil - }, - func(r textRecord) int { return utf8.RuneCount(r.Value.(textLine)) }, - ) - - // Format lists of simple lists in a batched form. - // If the list is sequence of only textLine values, - // then batch multiple values on a single line. - var isSimple bool - for _, r := range s { - _, isLine := r.Value.(textLine) - isSimple = r.Diff == 0 && r.Key == "" && isLine && r.Comment == nil - if !isSimple { - break - } - } - if isSimple { - n++ - var batch []byte - emitBatch := func() { - if len(batch) > 0 { - b = n.appendIndent(append(b, '\n'), d) - b = append(b, bytes.TrimRight(batch, " ")...) - batch = batch[:0] - } - } - for _, r := range s { - line := r.Value.(textLine) - if len(batch)+len(line)+len(", ") > maxColumnLength { - emitBatch() - } - batch = append(batch, line...) - batch = append(batch, ", "...) - } - emitBatch() - n-- - return n.appendIndent(append(b, '\n'), d) - } - - // Format the list as a multi-lined output. - n++ - for i, r := range s { - b = n.appendIndent(append(b, '\n'), d|r.Diff) - if r.Key != "" { - b = append(b, r.Key+": "...) - } - b = alignKeyLens[i].appendChar(b, ' ') - - b = r.Value.formatExpandedTo(b, d|r.Diff, n) - if !r.ElideComma { - b = append(b, ',') - } - b = alignValueLens[i].appendChar(b, ' ') - - if r.Comment != nil { - b = append(b, " // "+r.Comment.String()...) - } - } - n-- - - return n.appendIndent(append(b, '\n'), d) -} - -func (s textList) alignLens( - skipFunc func(textRecord) bool, - lenFunc func(textRecord) int, -) []repeatCount { - var startIdx, endIdx, maxLen int - lens := make([]repeatCount, len(s)) - for i, r := range s { - if skipFunc(r) { - for j := startIdx; j < endIdx && j < len(s); j++ { - lens[j] = repeatCount(maxLen - lenFunc(s[j])) - } - startIdx, endIdx, maxLen = i+1, i+1, 0 - } else { - if maxLen < lenFunc(r) { - maxLen = lenFunc(r) - } - endIdx = i + 1 - } - } - for j := startIdx; j < endIdx && j < len(s); j++ { - lens[j] = repeatCount(maxLen - lenFunc(s[j])) - } - return lens -} - -// textLine is a single-line segment of text and is always a leaf node -// in the textNode tree. -type textLine []byte - -var ( - textNil = textLine("nil") - textEllipsis = textLine("...") -) - -func (s textLine) Len() int { - return len(s) -} -func (s1 textLine) Equal(s2 textNode) bool { - if s2, ok := s2.(textLine); ok { - return bytes.Equal([]byte(s1), []byte(s2)) - } - return false -} -func (s textLine) String() string { - return string(s) -} -func (s textLine) formatCompactTo(b []byte, d diffMode) ([]byte, textNode) { - return append(b, s...), s -} -func (s textLine) formatExpandedTo(b []byte, _ diffMode, _ indentMode) []byte { - return append(b, s...) -} - -type diffStats struct { - Name string - NumIgnored int - NumIdentical int - NumRemoved int - NumInserted int - NumModified int -} - -func (s diffStats) IsZero() bool { - s.Name = "" - return s == diffStats{} -} - -func (s diffStats) NumDiff() int { - return s.NumRemoved + s.NumInserted + s.NumModified -} - -func (s diffStats) Append(ds diffStats) diffStats { - assert(s.Name == ds.Name) - s.NumIgnored += ds.NumIgnored - s.NumIdentical += ds.NumIdentical - s.NumRemoved += ds.NumRemoved - s.NumInserted += ds.NumInserted - s.NumModified += ds.NumModified - return s -} - -// String prints a humanly-readable summary of coalesced records. -// -// Example: -// diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" -func (s diffStats) String() string { - var ss []string - var sum int - labels := [...]string{"ignored", "identical", "removed", "inserted", "modified"} - counts := [...]int{s.NumIgnored, s.NumIdentical, s.NumRemoved, s.NumInserted, s.NumModified} - for i, n := range counts { - if n > 0 { - ss = append(ss, fmt.Sprintf("%d %v", n, labels[i])) - } - sum += n - } - - // Pluralize the name (adjusting for some obscure English grammar rules). - name := s.Name - if sum > 1 { - name += "s" - if strings.HasSuffix(name, "ys") { - name = name[:len(name)-2] + "ies" // e.g., "entrys" => "entries" - } - } - - // Format the list according to English grammar (with Oxford comma). - switch n := len(ss); n { - case 0: - return "" - case 1, 2: - return strings.Join(ss, " and ") + " " + name - default: - return strings.Join(ss[:n-1], ", ") + ", and " + ss[n-1] + " " + name - } -} - -type commentString string - -func (s commentString) String() string { return string(s) } diff --git a/test/vendor/github.com/google/go-cmp/cmp/report_value.go b/test/vendor/github.com/google/go-cmp/cmp/report_value.go deleted file mode 100644 index 668d470fd8..0000000000 --- a/test/vendor/github.com/google/go-cmp/cmp/report_value.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2019, The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cmp - -import "reflect" - -// valueNode represents a single node within a report, which is a -// structured representation of the value tree, containing information -// regarding which nodes are equal or not. -type valueNode struct { - parent *valueNode - - Type reflect.Type - ValueX reflect.Value - ValueY reflect.Value - - // NumSame is the number of leaf nodes that are equal. - // All descendants are equal only if NumDiff is 0. - NumSame int - // NumDiff is the number of leaf nodes that are not equal. - NumDiff int - // NumIgnored is the number of leaf nodes that are ignored. - NumIgnored int - // NumCompared is the number of leaf nodes that were compared - // using an Equal method or Comparer function. - NumCompared int - // NumTransformed is the number of non-leaf nodes that were transformed. - NumTransformed int - // NumChildren is the number of transitive descendants of this node. - // This counts from zero; thus, leaf nodes have no descendants. - NumChildren int - // MaxDepth is the maximum depth of the tree. This counts from zero; - // thus, leaf nodes have a depth of zero. - MaxDepth int - - // Records is a list of struct fields, slice elements, or map entries. - Records []reportRecord // If populated, implies Value is not populated - - // Value is the result of a transformation, pointer indirect, of - // type assertion. - Value *valueNode // If populated, implies Records is not populated - - // TransformerName is the name of the transformer. - TransformerName string // If non-empty, implies Value is populated -} -type reportRecord struct { - Key reflect.Value // Invalid for slice element - Value *valueNode -} - -func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) { - vx, vy := ps.Values() - child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy} - switch s := ps.(type) { - case StructField: - assert(parent.Value == nil) - parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child}) - case SliceIndex: - assert(parent.Value == nil) - parent.Records = append(parent.Records, reportRecord{Value: child}) - case MapIndex: - assert(parent.Value == nil) - parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child}) - case Indirect: - assert(parent.Value == nil && parent.Records == nil) - parent.Value = child - case TypeAssertion: - assert(parent.Value == nil && parent.Records == nil) - parent.Value = child - case Transform: - assert(parent.Value == nil && parent.Records == nil) - parent.Value = child - parent.TransformerName = s.Name() - parent.NumTransformed++ - default: - assert(parent == nil) // Must be the root step - } - return child -} - -func (r *valueNode) Report(rs Result) { - assert(r.MaxDepth == 0) // May only be called on leaf nodes - - if rs.ByIgnore() { - r.NumIgnored++ - } else { - if rs.Equal() { - r.NumSame++ - } else { - r.NumDiff++ - } - } - assert(r.NumSame+r.NumDiff+r.NumIgnored == 1) - - if rs.ByMethod() { - r.NumCompared++ - } - if rs.ByFunc() { - r.NumCompared++ - } - assert(r.NumCompared <= 1) -} - -func (child *valueNode) PopStep() (parent *valueNode) { - if child.parent == nil { - return nil - } - parent = child.parent - parent.NumSame += child.NumSame - parent.NumDiff += child.NumDiff - parent.NumIgnored += child.NumIgnored - parent.NumCompared += child.NumCompared - parent.NumTransformed += child.NumTransformed - parent.NumChildren += child.NumChildren + 1 - if parent.MaxDepth < child.MaxDepth+1 { - parent.MaxDepth = child.MaxDepth + 1 - } - return parent -} diff --git a/test/vendor/modules.txt b/test/vendor/modules.txt index 04a05ff876..b0d29873d5 100644 --- a/test/vendor/modules.txt +++ b/test/vendor/modules.txt @@ -28,6 +28,7 @@ github.com/Microsoft/hcsshim/internal/credentials github.com/Microsoft/hcsshim/internal/devices github.com/Microsoft/hcsshim/internal/extendedtask github.com/Microsoft/hcsshim/internal/gcs +github.com/Microsoft/hcsshim/internal/guest/spec github.com/Microsoft/hcsshim/internal/guestpath github.com/Microsoft/hcsshim/internal/hcs github.com/Microsoft/hcsshim/internal/hcs/resourcepaths @@ -206,13 +207,6 @@ github.com/golang/protobuf/ptypes github.com/golang/protobuf/ptypes/any github.com/golang/protobuf/ptypes/duration github.com/golang/protobuf/ptypes/timestamp -# github.com/google/go-cmp v0.5.6 -## explicit; go 1.8 -github.com/google/go-cmp/cmp -github.com/google/go-cmp/cmp/internal/diff -github.com/google/go-cmp/cmp/internal/flags -github.com/google/go-cmp/cmp/internal/function -github.com/google/go-cmp/cmp/internal/value # github.com/google/go-containerregistry v0.5.1 ## explicit; go 1.14 github.com/google/go-containerregistry/internal/and From 2baf93b5ccc718a23627df219a66a8cda63d1c98 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy <84944216+helsaawy@users.noreply.github.com> Date: Fri, 15 Apr 2022 12:25:05 -0400 Subject: [PATCH 05/16] removing global setting dependence from shim publisher (#1343) Signed-off-by: Hamza El-Saawy --- cmd/containerd-shim-runhcs-v1/events.go | 8 +++++--- cmd/containerd-shim-runhcs-v1/events_test.go | 2 +- cmd/containerd-shim-runhcs-v1/serve.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/containerd-shim-runhcs-v1/events.go b/cmd/containerd-shim-runhcs-v1/events.go index f15479c282..71ec3efdc6 100644 --- a/cmd/containerd-shim-runhcs-v1/events.go +++ b/cmd/containerd-shim-runhcs-v1/events.go @@ -17,17 +17,19 @@ type publisher interface { } type eventPublisher struct { + namespace string remotePublisher *shim.RemoteEventsPublisher } -var _ = (publisher)(&eventPublisher{}) +var _ publisher = &eventPublisher{} -func newEventPublisher(address string) (*eventPublisher, error) { +func newEventPublisher(address, namespace string) (*eventPublisher, error) { p, err := shim.NewPublisher(address) if err != nil { return nil, err } return &eventPublisher{ + namespace: namespace, remotePublisher: p, }, nil } @@ -48,5 +50,5 @@ func (e *eventPublisher) publishEvent(ctx context.Context, topic string, event i return nil } - return e.remotePublisher.Publish(namespaces.WithNamespace(ctx, namespaceFlag), topic, event) + return e.remotePublisher.Publish(namespaces.WithNamespace(ctx, e.namespace), topic, event) } diff --git a/cmd/containerd-shim-runhcs-v1/events_test.go b/cmd/containerd-shim-runhcs-v1/events_test.go index 8c70c1043c..51dfba28a8 100644 --- a/cmd/containerd-shim-runhcs-v1/events_test.go +++ b/cmd/containerd-shim-runhcs-v1/events_test.go @@ -8,7 +8,7 @@ type fakePublisher struct { events []interface{} } -var _ = (publisher)(&fakePublisher{}) +var _ publisher = &fakePublisher{} func newFakePublisher() *fakePublisher { return &fakePublisher{} diff --git a/cmd/containerd-shim-runhcs-v1/serve.go b/cmd/containerd-shim-runhcs-v1/serve.go index 47e204cbb6..9b1213b087 100644 --- a/cmd/containerd-shim-runhcs-v1/serve.go +++ b/cmd/containerd-shim-runhcs-v1/serve.go @@ -177,7 +177,7 @@ var serveCommand = cli.Command{ } ttrpcAddress := os.Getenv(ttrpcAddressEnv) - ttrpcEventPublisher, err := newEventPublisher(ttrpcAddress) + ttrpcEventPublisher, err := newEventPublisher(ttrpcAddress, namespaceFlag) if err != nil { return err } From 54a5ad86808d761e3e396aff3e2022840f39f9a8 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy <84944216+helsaawy@users.noreply.github.com> Date: Mon, 18 Apr 2022 15:16:58 -0400 Subject: [PATCH 06/16] Reorganizing makefile and adding info to rootfs (#1350) * Reorganizing makefile and adding info to rootfs Reorganized makefile to read from top to bottom, and added additional files to LCOW rootfs that include the time stamp of of the vhd creation, the image build date, and its full name (pulled from a *.testdata.json file in the LSG release, that appears to be one of the only location of that information). Signed-off-by: Hamza El-Saawy * PR: checking if jq is installed Signed-off-by: Hamza El-Saawy --- Makefile | 55 ++++++++++--------- .../github.com/Microsoft/hcsshim/Makefile | 55 ++++++++++--------- 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/Makefile b/Makefile index c11786ce3a..ea0d88748d 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,8 @@ endif GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA) SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +# additional directories to search for rule prerequisites and targets +VPATH=$(SRCROOT) DELTA_TARGET=out/delta.tar.gz @@ -47,42 +49,47 @@ out/rootfs.vhd: out/rootfs.tar.gz bin/cmd/tar2ext4 gzip -f -d ./out/rootfs.tar.gz bin/cmd/tar2ext4 -vhd -i ./out/rootfs.tar -o $@ +out/rootfs.tar.gz: out/initrd.img + rm -rf rootfs-conv + mkdir rootfs-conv + gunzip -c out/initrd.img | (cd rootfs-conv && cpio -imd) + tar -zcf $@ -C rootfs-conv . + rm -rf rootfs-conv + +out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh + $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed + gzip -c out/initrd.img.uncompressed > $@ + rm out/initrd.img.uncompressed + +# This target includes utilities which may be useful for testing purposes. +out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report + rm -rf rootfs-dev + mkdir rootfs-dev + tar -xzf out/delta.tar.gz -C rootfs-dev + cp bin/internal/tools/snp-report rootfs-dev/bin/ + tar -zcf $@ -C rootfs-dev . + rm -rf rootfs-dev + out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools bin/cmd/hooks/wait-paths Makefile @mkdir -p out rm -rf rootfs mkdir -p rootfs/bin/ + mkdir -p rootfs/info/ cp bin/init rootfs/ cp bin/vsockexec rootfs/bin/ cp bin/cmd/gcs rootfs/bin/ cp bin/cmd/gcstools rootfs/bin/ cp bin/cmd/hooks/wait-paths rootfs/bin/ for tool in $(GCS_TOOLS); do ln -s gcstools rootfs/bin/$$tool; done - git -C $(SRCROOT) rev-parse HEAD > rootfs/gcs.commit && \ - git -C $(SRCROOT) rev-parse --abbrev-ref HEAD > rootfs/gcs.branch + git -C $(SRCROOT) rev-parse HEAD > rootfs/info/gcs.commit && \ + git -C $(SRCROOT) rev-parse --abbrev-ref HEAD > rootfs/info/gcs.branch && \ + date --iso-8601=minute --utc > rootfs/info/tar.date + $(if $(and $(realpath $(subst .tar,.testdata.json,$(BASE))), $(shell which jq)), \ + jq -r '.IMAGE_NAME' $(subst .tar,.testdata.json,$(BASE)) 2>/dev/null > rootfs/info/image.name && \ + jq -r '.DATETIME' $(subst .tar,.testdata.json,$(BASE)) 2>/dev/null > rootfs/info/build.date) tar -zcf $@ -C rootfs . rm -rf rootfs -# This target includes utilities which may be useful for testing purposes. -out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report - rm -rf rootfs-dev - mkdir rootfs-dev - tar -xzf out/delta.tar.gz -C rootfs-dev - cp bin/internal/tools/snp-report rootfs-dev/bin/ - tar -zcf $@ -C rootfs-dev . - rm -rf rootfs-dev - -out/rootfs.tar.gz: out/initrd.img - rm -rf rootfs-conv - mkdir rootfs-conv - gunzip -c out/initrd.img | (cd rootfs-conv && cpio -imd) - tar -zcf $@ -C rootfs-conv . - rm -rf rootfs-conv - -out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh - $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed - gzip -c out/initrd.img.uncompressed > $@ - rm out/initrd.img.uncompressed - -include deps/cmd/gcs.gomake -include deps/cmd/gcstools.gomake -include deps/cmd/hooks/wait-paths.gomake @@ -101,8 +108,6 @@ out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh @/bin/echo -e '-include $(@:%.gomake=%.godeps)' >> $@.new mv $@.new $@ -VPATH=$(SRCROOT) - bin/vsockexec: vsockexec/vsockexec.o vsockexec/vsock.o @mkdir -p bin $(CC) $(LDFLAGS) -o $@ $^ diff --git a/test/vendor/github.com/Microsoft/hcsshim/Makefile b/test/vendor/github.com/Microsoft/hcsshim/Makefile index c11786ce3a..ea0d88748d 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/Makefile +++ b/test/vendor/github.com/Microsoft/hcsshim/Makefile @@ -16,6 +16,8 @@ endif GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA) SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST)))) +# additional directories to search for rule prerequisites and targets +VPATH=$(SRCROOT) DELTA_TARGET=out/delta.tar.gz @@ -47,42 +49,47 @@ out/rootfs.vhd: out/rootfs.tar.gz bin/cmd/tar2ext4 gzip -f -d ./out/rootfs.tar.gz bin/cmd/tar2ext4 -vhd -i ./out/rootfs.tar -o $@ +out/rootfs.tar.gz: out/initrd.img + rm -rf rootfs-conv + mkdir rootfs-conv + gunzip -c out/initrd.img | (cd rootfs-conv && cpio -imd) + tar -zcf $@ -C rootfs-conv . + rm -rf rootfs-conv + +out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh + $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed + gzip -c out/initrd.img.uncompressed > $@ + rm out/initrd.img.uncompressed + +# This target includes utilities which may be useful for testing purposes. +out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report + rm -rf rootfs-dev + mkdir rootfs-dev + tar -xzf out/delta.tar.gz -C rootfs-dev + cp bin/internal/tools/snp-report rootfs-dev/bin/ + tar -zcf $@ -C rootfs-dev . + rm -rf rootfs-dev + out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools bin/cmd/hooks/wait-paths Makefile @mkdir -p out rm -rf rootfs mkdir -p rootfs/bin/ + mkdir -p rootfs/info/ cp bin/init rootfs/ cp bin/vsockexec rootfs/bin/ cp bin/cmd/gcs rootfs/bin/ cp bin/cmd/gcstools rootfs/bin/ cp bin/cmd/hooks/wait-paths rootfs/bin/ for tool in $(GCS_TOOLS); do ln -s gcstools rootfs/bin/$$tool; done - git -C $(SRCROOT) rev-parse HEAD > rootfs/gcs.commit && \ - git -C $(SRCROOT) rev-parse --abbrev-ref HEAD > rootfs/gcs.branch + git -C $(SRCROOT) rev-parse HEAD > rootfs/info/gcs.commit && \ + git -C $(SRCROOT) rev-parse --abbrev-ref HEAD > rootfs/info/gcs.branch && \ + date --iso-8601=minute --utc > rootfs/info/tar.date + $(if $(and $(realpath $(subst .tar,.testdata.json,$(BASE))), $(shell which jq)), \ + jq -r '.IMAGE_NAME' $(subst .tar,.testdata.json,$(BASE)) 2>/dev/null > rootfs/info/image.name && \ + jq -r '.DATETIME' $(subst .tar,.testdata.json,$(BASE)) 2>/dev/null > rootfs/info/build.date) tar -zcf $@ -C rootfs . rm -rf rootfs -# This target includes utilities which may be useful for testing purposes. -out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report - rm -rf rootfs-dev - mkdir rootfs-dev - tar -xzf out/delta.tar.gz -C rootfs-dev - cp bin/internal/tools/snp-report rootfs-dev/bin/ - tar -zcf $@ -C rootfs-dev . - rm -rf rootfs-dev - -out/rootfs.tar.gz: out/initrd.img - rm -rf rootfs-conv - mkdir rootfs-conv - gunzip -c out/initrd.img | (cd rootfs-conv && cpio -imd) - tar -zcf $@ -C rootfs-conv . - rm -rf rootfs-conv - -out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh - $(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed - gzip -c out/initrd.img.uncompressed > $@ - rm out/initrd.img.uncompressed - -include deps/cmd/gcs.gomake -include deps/cmd/gcstools.gomake -include deps/cmd/hooks/wait-paths.gomake @@ -101,8 +108,6 @@ out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh @/bin/echo -e '-include $(@:%.gomake=%.godeps)' >> $@.new mv $@.new $@ -VPATH=$(SRCROOT) - bin/vsockexec: vsockexec/vsockexec.o vsockexec/vsock.o @mkdir -p bin $(CC) $(LDFLAGS) -o $@ $^ From a4c9777c1fa101d581f8facd257281349853be29 Mon Sep 17 00:00:00 2001 From: Danny Canter <36526702+dcantah@users.noreply.github.com> Date: Mon, 18 Apr 2022 13:23:05 -0700 Subject: [PATCH 07/16] Use /internal/memory constants (#1354) * Use /internal/memory constants We have a bunch of 1024 * 1024 or 1024 * 1024 * 1024 numerical constants (or just other megabyte constants) lying around. This changes to using the constants we have defined in the /internal/memory package. This additionally changes the names of the constants from MegaByte/GigaByte to MiB/GiB and changes them to untyped constants. Signed-off-by: Daniel Canter --- cmd/containerd-shim-runhcs-v1/delete.go | 3 +- cmd/containerd-shim-runhcs-v1/task_hcs.go | 5 ++-- computestorage/helpers.go | 9 +++--- ext4/dmverity/dmverity.go | 5 ++-- ext4/internal/compactext4/compact.go | 9 +++--- ext4/internal/compactext4/compact_test.go | 7 +++-- internal/guest/storage/crypt/crypt_test.go | 9 +++--- internal/guest/storage/overlay/overlay.go | 2 +- internal/jobcontainers/oci.go | 3 +- internal/memory/pool.go | 6 ++-- internal/memory/pool_test.go | 28 +++++++++---------- internal/memory/types.go | 4 +-- internal/tools/uvmboot/lcow.go | 3 +- internal/uvm/constants.go | 3 +- internal/uvm/memory_update.go | 8 ++---- internal/uvm/vpmem_mapped_test.go | 6 ++-- test/cri-containerd/container_update_test.go | 3 +- test/cri-containerd/createcontainer_test.go | 7 +++-- test/cri-containerd/pod_update_test.go | 5 ++-- test/cri-containerd/stats_test.go | 6 ++-- test/functional/test.go | 4 --- test/functional/uvm_memory_test.go | 5 ++-- .../hcsshim/computestorage/helpers.go | 9 +++--- .../hcsshim/ext4/dmverity/dmverity.go | 5 ++-- .../ext4/internal/compactext4/compact.go | 9 +++--- .../Microsoft/hcsshim/internal/memory/pool.go | 6 ++-- .../hcsshim/internal/memory/types.go | 4 +-- .../hcsshim/internal/uvm/constants.go | 3 +- .../hcsshim/internal/uvm/memory_update.go | 8 ++---- 29 files changed, 97 insertions(+), 87 deletions(-) diff --git a/cmd/containerd-shim-runhcs-v1/delete.go b/cmd/containerd-shim-runhcs-v1/delete.go index 69905c0f72..3fc4447c68 100644 --- a/cmd/containerd-shim-runhcs-v1/delete.go +++ b/cmd/containerd-shim-runhcs-v1/delete.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Microsoft/hcsshim/internal/hcs" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/oc" "github.com/Microsoft/hcsshim/internal/winapi" "github.com/containerd/containerd/runtime/v2/task" @@ -69,7 +70,7 @@ The delete command will be executed in the container's bundle as its cwd. // This should be done as the first thing so that we don't miss any panic logs even if // something goes wrong during delete op. // The file can be very large so read only first 1MB of data. - readLimit := int64(1024 * 1024) // 1MB + readLimit := int64(memory.MiB) // 1MB logBytes, err := limitedRead(filepath.Join(bundleFlag, "panic.log"), readLimit) if err == nil && len(logBytes) > 0 { if int64(len(logBytes)) == readLimit { diff --git a/cmd/containerd-shim-runhcs-v1/task_hcs.go b/cmd/containerd-shim-runhcs-v1/task_hcs.go index d32fade6e5..32f6a2055f 100644 --- a/cmd/containerd-shim-runhcs-v1/task_hcs.go +++ b/cmd/containerd-shim-runhcs-v1/task_hcs.go @@ -31,6 +31,7 @@ import ( "github.com/Microsoft/hcsshim/internal/hcsoci" "github.com/Microsoft/hcsshim/internal/jobcontainers" "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/oci" "github.com/Microsoft/hcsshim/internal/processorinfo" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" @@ -42,8 +43,6 @@ import ( "github.com/Microsoft/hcsshim/pkg/annotations" ) -const bytesPerMB = 1024 * 1024 - func newHcsStandaloneTask(ctx context.Context, events publisher, req *task.CreateTaskRequest, s *specs.Spec) (shimTask, error) { log.G(ctx).WithField("tid", req.ID).Debug("newHcsStandaloneTask") @@ -1008,7 +1007,7 @@ func (ht *hcsTask) updateWCOWResources(ctx context.Context, data interface{}, an return errors.New("must have resources be type *WindowsResources when updating a wcow container") } if resources.Memory != nil && resources.Memory.Limit != nil { - newMemorySizeInMB := *resources.Memory.Limit / bytesPerMB + newMemorySizeInMB := *resources.Memory.Limit / memory.MiB memoryLimit := hcsoci.NormalizeMemorySize(ctx, ht.id, newMemorySizeInMB) if err := ht.requestUpdateContainer(ctx, resourcepaths.SiloMemoryResourcePath, memoryLimit); err != nil { return err diff --git a/computestorage/helpers.go b/computestorage/helpers.go index 3bbbc226c0..a0e329edec 100644 --- a/computestorage/helpers.go +++ b/computestorage/helpers.go @@ -10,6 +10,7 @@ import ( "github.com/Microsoft/go-winio/pkg/security" "github.com/Microsoft/go-winio/vhd" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/pkg/errors" "golang.org/x/sys/windows" ) @@ -61,8 +62,8 @@ func SetupContainerBaseLayer(ctx context.Context, layerPath, baseVhdPath, diffVh createParams := &vhd.CreateVirtualDiskParameters{ Version: 2, Version2: vhd.CreateVersion2{ - MaximumSize: sizeInGB * 1024 * 1024 * 1024, - BlockSizeInBytes: defaultVHDXBlockSizeInMB * 1024 * 1024, + MaximumSize: sizeInGB * memory.GiB, + BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB, }, } handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams) @@ -137,8 +138,8 @@ func SetupUtilityVMBaseLayer(ctx context.Context, uvmPath, baseVhdPath, diffVhdP createParams := &vhd.CreateVirtualDiskParameters{ Version: 2, Version2: vhd.CreateVersion2{ - MaximumSize: sizeInGB * 1024 * 1024 * 1024, - BlockSizeInBytes: defaultVHDXBlockSizeInMB * 1024 * 1024, + MaximumSize: sizeInGB * memory.GiB, + BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB, }, } handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams) diff --git a/ext4/dmverity/dmverity.go b/ext4/dmverity/dmverity.go index f952fbde87..0e42037d98 100644 --- a/ext4/dmverity/dmverity.go +++ b/ext4/dmverity/dmverity.go @@ -14,14 +14,15 @@ import ( "github.com/pkg/errors" "github.com/Microsoft/hcsshim/ext4/internal/compactext4" + "github.com/Microsoft/hcsshim/internal/memory" ) const ( blockSize = compactext4.BlockSize // MerkleTreeBufioSize is a default buffer size to use with bufio.Reader - MerkleTreeBufioSize = 1024 * 1024 // 1MB + MerkleTreeBufioSize = memory.MiB // 1MB // RecommendedVHDSizeGB is the recommended size in GB for VHDs, which is not a hard limit. - RecommendedVHDSizeGB = 128 * 1024 * 1024 * 1024 + RecommendedVHDSizeGB = 128 * memory.GiB // VeritySignature is a value written to dm-verity super-block. VeritySignature = "verity" ) diff --git a/ext4/internal/compactext4/compact.go b/ext4/internal/compactext4/compact.go index e64d86eba4..504437270b 100644 --- a/ext4/internal/compactext4/compact.go +++ b/ext4/internal/compactext4/compact.go @@ -13,6 +13,7 @@ import ( "time" "github.com/Microsoft/hcsshim/ext4/internal/format" + "github.com/Microsoft/hcsshim/internal/memory" ) // Writer writes a compact ext4 file system. @@ -101,15 +102,15 @@ const ( maxInodesPerGroup = BlockSize * 8 // Limited by the inode bitmap inodesPerGroupIncrement = BlockSize / inodeSize - defaultMaxDiskSize = 16 * 1024 * 1024 * 1024 // 16GB + defaultMaxDiskSize = 16 * memory.GiB // 16GB maxMaxDiskSize = 16 * 1024 * 1024 * 1024 * 1024 // 16TB groupDescriptorSize = 32 // Use the small group descriptor groupsPerDescriptorBlock = BlockSize / groupDescriptorSize - maxFileSize = 128 * 1024 * 1024 * 1024 // 128GB file size maximum for now - smallSymlinkSize = 59 // max symlink size that goes directly in the inode - maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent + maxFileSize = 128 * memory.GiB // 128GB file size maximum for now + smallSymlinkSize = 59 // max symlink size that goes directly in the inode + maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent inodeDataSize = 60 inodeUsedSize = 152 // fields through CrtimeExtra inodeExtraSize = inodeSize - inodeUsedSize diff --git a/ext4/internal/compactext4/compact_test.go b/ext4/internal/compactext4/compact_test.go index 7271446756..95ba44c333 100644 --- a/ext4/internal/compactext4/compact_test.go +++ b/ext4/internal/compactext4/compact_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/Microsoft/hcsshim/ext4/internal/format" + "github.com/Microsoft/hcsshim/internal/memory" ) type testFile struct { @@ -318,9 +319,9 @@ func TestTime(t *testing.T) { func TestLargeFile(t *testing.T) { testFiles := []testFile{ - {Path: "small", File: &File{}, DataSize: 1024 * 1024}, // can't change type - {Path: "medium", File: &File{}, DataSize: 200 * 1024 * 1024}, // can't change type - {Path: "large", File: &File{}, DataSize: 600 * 1024 * 1024}, // can't change type + {Path: "small", File: &File{}, DataSize: memory.MiB}, // can't change type + {Path: "medium", File: &File{}, DataSize: 200 * memory.MiB}, // can't change type + {Path: "large", File: &File{}, DataSize: 600 * memory.MiB}, // can't change type } runTestsOnFiles(t, testFiles) } diff --git a/internal/guest/storage/crypt/crypt_test.go b/internal/guest/storage/crypt/crypt_test.go index b5f31599c6..9568a15f2b 100644 --- a/internal/guest/storage/crypt/crypt_test.go +++ b/internal/guest/storage/crypt/crypt_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/pkg/errors" ) @@ -194,7 +195,7 @@ func Test_Encrypt_Create_Sparse_File_Error(t *testing.T) { // Test what happens when it isn't possible to create a sparse file, and // make sure that _createSparseEmptyFile receives the right arguments. - blockDeviceSize := int64(1024 * 1024 * 1024) + blockDeviceSize := int64(memory.GiB) _generateKeyFile = func(path string, size int64) error { return nil @@ -246,7 +247,7 @@ func Test_Encrypt_Mkfs_Error(t *testing.T) { // Test what happens when mkfs fails to format the unencrypted device. // Verify that the arguments passed to it are the right ones. - blockDeviceSize := int64(1024 * 1024 * 1024) + blockDeviceSize := int64(memory.GiB) _generateKeyFile = func(path string, size int64) error { return nil @@ -296,7 +297,7 @@ func Test_Encrypt_Sparse_Copy_Error(t *testing.T) { // Test what happens when the sparse copy fails. Verify that the arguments // passed to it are the right ones. - blockDeviceSize := int64(1024 * 1024 * 1024) + blockDeviceSize := int64(memory.GiB) _generateKeyFile = func(path string, size int64) error { return nil @@ -354,7 +355,7 @@ func Test_Encrypt_Success(t *testing.T) { // Test what happens when everything goes right. - blockDeviceSize := int64(1024 * 1024 * 1024) + blockDeviceSize := int64(memory.GiB) _generateKeyFile = func(path string, size int64) error { return nil diff --git a/internal/guest/storage/overlay/overlay.go b/internal/guest/storage/overlay/overlay.go index 93c7fa88d7..f108dba3b0 100644 --- a/internal/guest/storage/overlay/overlay.go +++ b/internal/guest/storage/overlay/overlay.go @@ -44,7 +44,7 @@ func processErrNoSpace(ctx context.Context, path string, err error) { used := all - free toGigabyteStr := func(val uint64) string { - return fmt.Sprintf("%.1f", float64(val)/float64(memory.GigaByte)) + return fmt.Sprintf("%.1f", float64(val)/float64(memory.GiB)) } log.G(ctx).WithFields(logrus.Fields{ diff --git a/internal/jobcontainers/oci.go b/internal/jobcontainers/oci.go index ef8a17a1f8..1e0005854c 100644 --- a/internal/jobcontainers/oci.go +++ b/internal/jobcontainers/oci.go @@ -7,6 +7,7 @@ import ( "github.com/Microsoft/hcsshim/internal/hcsoci" "github.com/Microsoft/hcsshim/internal/jobobject" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/processorinfo" "github.com/Microsoft/hcsshim/pkg/annotations" @@ -57,7 +58,7 @@ func specToLimits(ctx context.Context, cid string, s *specs.Spec) (*jobobject.Jo CPUWeight: realCPUWeight, MaxIOPS: maxIops, MaxBandwidth: maxBandwidth, - MemoryLimitInBytes: memLimitMB * 1024 * 1024, + MemoryLimitInBytes: memLimitMB * memory.MiB, }, nil } diff --git a/internal/memory/pool.go b/internal/memory/pool.go index 6381dfd887..1ef5814d7e 100644 --- a/internal/memory/pool.go +++ b/internal/memory/pool.go @@ -5,8 +5,8 @@ import ( ) const ( - minimumClassSize = MegaByte - maximumClassSize = 4 * GigaByte + minimumClassSize = MiB + maximumClassSize = 4 * GiB memoryClassNumber = 7 ) @@ -179,7 +179,7 @@ func (pa *PoolAllocator) findNextOffset(memCls classType) (classType, uint64, er continue } - target := maximumClassSize + target := uint64(maximumClassSize) for offset := range pi.free { if offset < target { target = offset diff --git a/internal/memory/pool_test.go b/internal/memory/pool_test.go index 4098ae5106..e331fa14b8 100644 --- a/internal/memory/pool_test.go +++ b/internal/memory/pool_test.go @@ -38,13 +38,13 @@ func Test_MemAlloc_allocate_without_expand(t *testing.T) { offset: 0, } - testAllocate(t, ma, MegaByte) + testAllocate(t, ma, MiB) } func Test_MemAlloc_allocate_not_enough_space(t *testing.T) { ma := &PoolAllocator{} - _, err := ma.Allocate(MegaByte) + _, err := ma.Allocate(MiB) if err == nil { t.Fatal("expected error, got nil") } @@ -72,7 +72,7 @@ func Test_MemAlloc_expand(t *testing.T) { poolCls0 := pa.pools[0] for i := 0; i < 4; i++ { - offset := uint64(i) * MegaByte + offset := uint64(i) * MiB _, ok := poolCls0.free[offset] if !ok { t.Fatalf("did not find region with offset=%d", offset) @@ -88,12 +88,12 @@ func Test_MemAlloc_expand(t *testing.T) { func Test_MemAlloc_allocate_automatically_expands(t *testing.T) { pa := &PoolAllocator{} pa.pools[2] = newEmptyMemoryPool() - pa.pools[2].free[MegaByte] = ®ion{ + pa.pools[2].free[MiB] = ®ion{ class: 2, - offset: MegaByte, + offset: MiB, } - testAllocate(t, pa, MegaByte) + testAllocate(t, pa, MiB) if pa.pools[1] == nil { t.Fatalf("memory not extended for class type 1") @@ -112,7 +112,7 @@ func Test_MemAlloc_alloc_and_release(t *testing.T) { } pa.pools[0].free[0] = r - testAllocate(t, pa, MegaByte) + testAllocate(t, pa, MiB) err := pa.Release(r) if err != nil { @@ -147,10 +147,10 @@ func Test_MemAlloc_release_invalid_offset(t *testing.T) { } pa.pools[0].free[0] = r - testAllocate(t, pa, MegaByte) + testAllocate(t, pa, MiB) // change the actual offset - r.offset = MegaByte + r.offset = MiB err := pa.Release(r) if err == nil { t.Fatal("no error returned") @@ -163,7 +163,7 @@ func Test_MemAlloc_release_invalid_offset(t *testing.T) { func Test_MemAlloc_Max_Out(t *testing.T) { ma := NewPoolMemoryAllocator() for i := 0; i < 4096; i++ { - _, err := ma.Allocate(MegaByte) + _, err := ma.Allocate(MiB) if err != nil { t.Fatalf("unexpected error during memory allocation: %s", err) } @@ -172,7 +172,7 @@ func Test_MemAlloc_Max_Out(t *testing.T) { t.Fatalf("expected 4096 busy blocks of class 0, got %d instead", len(ma.pools[0].busy)) } for i := 0; i < 4096; i++ { - offset := uint64(i) * MegaByte + offset := uint64(i) * MiB if _, ok := ma.pools[0].busy[offset]; !ok { t.Fatalf("expected to find offset %d", offset) } @@ -189,17 +189,17 @@ func Test_GetMemoryClass(t *testing.T) { testCases := []config{ { name: "Size_1MB_Class_0", - size: MegaByte, + size: MiB, expected: 0, }, { name: "Size_6MB_Class_2", - size: 6 * MegaByte, + size: 6 * MiB, expected: 2, }, { name: "Size_2GB_Class_6", - size: 2 * GigaByte, + size: 2 * GiB, expected: 6, }, } diff --git a/internal/memory/types.go b/internal/memory/types.go index 7cf4368a95..d6cdb8cc4c 100644 --- a/internal/memory/types.go +++ b/internal/memory/types.go @@ -5,8 +5,8 @@ import "github.com/pkg/errors" type classType uint32 const ( - MegaByte = uint64(1024 * 1024) - GigaByte = 1024 * MegaByte + MiB = 1024 * 1024 + GiB = 1024 * MiB ) var ( diff --git a/internal/tools/uvmboot/lcow.go b/internal/tools/uvmboot/lcow.go index e2c39af4f8..4d78c79848 100644 --- a/internal/tools/uvmboot/lcow.go +++ b/internal/tools/uvmboot/lcow.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/Microsoft/hcsshim/internal/cmd" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/uvm" "github.com/containerd/console" "github.com/sirupsen/logrus" @@ -111,7 +112,7 @@ var lcowCommand = cli.Command{ options.VPMemDeviceCount = uint32(c.Uint(vpMemMaxCountArgName)) } if c.IsSet(vpMemMaxSizeArgName) { - options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * 1024 * 1024 // convert from MB to bytes + options.VPMemSizeBytes = c.Uint64(vpMemMaxSizeArgName) * memory.MiB // convert from MB to bytes } if !useGcs { if c.IsSet(execCommandLineArgName) { diff --git a/internal/uvm/constants.go b/internal/uvm/constants.go index 4bd84f26d2..aebd58a391 100644 --- a/internal/uvm/constants.go +++ b/internal/uvm/constants.go @@ -3,6 +3,7 @@ package uvm import ( "errors" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" ) @@ -17,7 +18,7 @@ const ( // DefaultVPMemSizeBytes is the default size of a VPMem device if the create request // doesn't specify. - DefaultVPMemSizeBytes = 4 * 1024 * 1024 * 1024 // 4GB + DefaultVPMemSizeBytes = 4 * memory.GiB // 4GB ) var ( diff --git a/internal/uvm/memory_update.go b/internal/uvm/memory_update.go index 7a06f283dd..59444f417e 100644 --- a/internal/uvm/memory_update.go +++ b/internal/uvm/memory_update.go @@ -8,18 +8,16 @@ import ( "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/memory" ) -const ( - bytesPerPage = 4096 - bytesPerMB = 1024 * 1024 -) +const bytesPerPage = 4096 // UpdateMemory makes a call to the VM's orchestrator to update the VM's size in MB // Internally, HCS will get the number of pages this corresponds to and attempt to assign // pages to numa nodes evenly func (uvm *UtilityVM) UpdateMemory(ctx context.Context, sizeInBytes uint64) error { - requestedSizeInMB := sizeInBytes / bytesPerMB + requestedSizeInMB := sizeInBytes / memory.MiB actual := uvm.normalizeMemorySize(ctx, requestedSizeInMB) req := &hcsschema.ModifySettingRequest{ ResourcePath: resourcepaths.MemoryResourcePath, diff --git a/internal/uvm/vpmem_mapped_test.go b/internal/uvm/vpmem_mapped_test.go index 5ef9f1d257..144210c2eb 100644 --- a/internal/uvm/vpmem_mapped_test.go +++ b/internal/uvm/vpmem_mapped_test.go @@ -41,11 +41,11 @@ func setupNewVPMemScenario(ctx context.Context, t *testing.T, size uint64, hostP func Test_VPMem_MapDevice_New(t *testing.T) { // basic scenario already validated in the helper function - setupNewVPMemScenario(context.TODO(), t, memory.MegaByte, "foo", "bar") + setupNewVPMemScenario(context.TODO(), t, memory.MiB, "foo", "bar") } func Test_VPMem_UnmapDevice_With_Removal(t *testing.T) { - pmem, _ := setupNewVPMemScenario(context.TODO(), t, memory.MegaByte, "foo", "bar") + pmem, _ := setupNewVPMemScenario(context.TODO(), t, memory.MiB, "foo", "bar") err := pmem.unmapVHDLayer(context.TODO(), "foo") if err != nil { @@ -57,7 +57,7 @@ func Test_VPMem_UnmapDevice_With_Removal(t *testing.T) { } func Test_VPMem_UnmapDevice_Without_Removal(t *testing.T) { - pmem, mappedDevice := setupNewVPMemScenario(context.TODO(), t, memory.MegaByte, "foo", "bar") + pmem, mappedDevice := setupNewVPMemScenario(context.TODO(), t, memory.MiB, "foo", "bar") err := pmem.mapVHDLayer(context.TODO(), mappedDevice) if err != nil { t.Fatalf("unexpected error when mapping device: %s", err) diff --git a/test/cri-containerd/container_update_test.go b/test/cri-containerd/container_update_test.go index ccfdc8bb8d..1218183163 100644 --- a/test/cri-containerd/container_update_test.go +++ b/test/cri-containerd/container_update_test.go @@ -8,6 +8,7 @@ import ( "fmt" "testing" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/osversion" "github.com/Microsoft/hcsshim/pkg/annotations" testutilities "github.com/Microsoft/hcsshim/test/functional/utilities" @@ -302,7 +303,7 @@ func Test_Container_UpdateResources_Memory(t *testing.T) { defer removePodSandbox(t, client, ctx, podID) defer stopPodSandbox(t, client, ctx, podID) - var startingMemorySize int64 = 768 * 1024 * 1024 + var startingMemorySize int64 = 768 * memory.MiB containerRequest := &runtime.CreateContainerRequest{ Config: &runtime.ContainerConfig{ Metadata: &runtime.ContainerMetadata{ diff --git a/test/cri-containerd/createcontainer_test.go b/test/cri-containerd/createcontainer_test.go index 0efa1c62e3..c630e1dd39 100644 --- a/test/cri-containerd/createcontainer_test.go +++ b/test/cri-containerd/createcontainer_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/osversion" "github.com/Microsoft/hcsshim/pkg/annotations" testutilities "github.com/Microsoft/hcsshim/test/functional/utilities" @@ -276,7 +277,7 @@ func Test_CreateContainer_MemorySize_Config_WCOW_Process(t *testing.T) { }, Windows: &runtime.WindowsContainerConfig{ Resources: &runtime.WindowsContainerResources{ - MemoryLimitInBytes: 768 * 1024 * 1024, // 768MB + MemoryLimitInBytes: 768 * memory.MiB, // 768MB }, }, }, @@ -336,7 +337,7 @@ func Test_CreateContainer_MemorySize_Config_WCOW_Hypervisor(t *testing.T) { }, Windows: &runtime.WindowsContainerConfig{ Resources: &runtime.WindowsContainerResources{ - MemoryLimitInBytes: 768 * 1024 * 1024, // 768MB + MemoryLimitInBytes: 768 * memory.MiB, // 768MB }, }, }, @@ -392,7 +393,7 @@ func Test_CreateContainer_MemorySize_LCOW(t *testing.T) { }, Linux: &runtime.LinuxContainerConfig{ Resources: &runtime.LinuxContainerResources{ - MemoryLimitInBytes: 768 * 1024 * 1024, // 768MB + MemoryLimitInBytes: 768 * memory.MiB, // 768MB }, }, }, diff --git a/test/cri-containerd/pod_update_test.go b/test/cri-containerd/pod_update_test.go index bf1fc6f50f..9fe2286b7c 100644 --- a/test/cri-containerd/pod_update_test.go +++ b/test/cri-containerd/pod_update_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/Microsoft/hcsshim/internal/cpugroup" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/processorinfo" "github.com/Microsoft/hcsshim/pkg/annotations" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" @@ -45,7 +46,7 @@ func Test_Pod_UpdateResources_Memory(t *testing.T) { } else { pullRequiredImages(t, []string{test.sandboxImage}) } - var startingMemorySize int64 = 768 * 1024 * 1024 + var startingMemorySize int64 = 768 * memory.MiB podRequest := getRunPodSandboxRequest( t, test.runtimeHandler, @@ -116,7 +117,7 @@ func Test_Pod_UpdateResources_Memory_PA(t *testing.T) { } else { pullRequiredImages(t, []string{test.sandboxImage}) } - var startingMemorySize int64 = 200 * 1024 * 1024 + var startingMemorySize int64 = 200 * memory.MiB podRequest := getRunPodSandboxRequest( t, test.runtimeHandler, diff --git a/test/cri-containerd/stats_test.go b/test/cri-containerd/stats_test.go index f7a4906d40..7f052083ef 100644 --- a/test/cri-containerd/stats_test.go +++ b/test/cri-containerd/stats_test.go @@ -5,10 +5,12 @@ package cri_containerd import ( "context" - "github.com/Microsoft/hcsshim/pkg/annotations" "strconv" "testing" + "github.com/Microsoft/hcsshim/internal/memory" + "github.com/Microsoft/hcsshim/pkg/annotations" + runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) @@ -79,7 +81,7 @@ func verifyPhysicallyBackedWorkingSet(t *testing.T, num uint64, stat *runtime.Co if stat == nil { t.Fatal("expected stat to be non nil") } - numInBytes := num * 1024 * 1024 + numInBytes := num * memory.MiB if stat.Memory.WorkingSetBytes.Value != numInBytes { t.Fatalf("expected working set size to be %d bytes but got: %d", numInBytes, stat.Memory.WorkingSetBytes.Value) } diff --git a/test/functional/test.go b/test/functional/test.go index 28314d4928..e0e8f0545b 100644 --- a/test/functional/test.go +++ b/test/functional/test.go @@ -13,10 +13,6 @@ import ( "github.com/sirupsen/logrus" ) -const ( - bytesPerMB = 1024 * 1024 -) - var pauseDurationOnCreateContainerFailure time.Duration func init() { diff --git a/test/functional/uvm_memory_test.go b/test/functional/uvm_memory_test.go index bc1ecd5ea9..aad97c271b 100644 --- a/test/functional/uvm_memory_test.go +++ b/test/functional/uvm_memory_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/uvm" "github.com/Microsoft/hcsshim/osversion" testutilities "github.com/Microsoft/hcsshim/test/functional/utilities" @@ -22,7 +23,7 @@ func TestUVMMemoryUpdateLCOW(t *testing.T) { u := testutilities.CreateLCOWUVMFromOpts(ctx, t, opts) defer u.Close() - newMemorySize := uint64(opts.MemorySizeInMB/2) * bytesPerMB + newMemorySize := uint64(opts.MemorySizeInMB/2) * memory.MiB if err := u.UpdateMemory(ctx, newMemorySize); err != nil { t.Fatalf("failed to make call to modify UVM memory size in MB with: %v", err) @@ -49,7 +50,7 @@ func TestUVMMemoryUpdateWCOW(t *testing.T) { defer os.RemoveAll(uvmScratchDir) defer u.Close() - newMemoryInBytes := uint64(opts.MemorySizeInMB/2) * bytesPerMB + newMemoryInBytes := uint64(opts.MemorySizeInMB/2) * memory.MiB if err := u.UpdateMemory(ctx, newMemoryInBytes); err != nil { t.Fatalf("failed to make call to modify UVM memory size in MB with: %v", err) } diff --git a/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go b/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go index 3bbbc226c0..a0e329edec 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go +++ b/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go @@ -10,6 +10,7 @@ import ( "github.com/Microsoft/go-winio/pkg/security" "github.com/Microsoft/go-winio/vhd" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/pkg/errors" "golang.org/x/sys/windows" ) @@ -61,8 +62,8 @@ func SetupContainerBaseLayer(ctx context.Context, layerPath, baseVhdPath, diffVh createParams := &vhd.CreateVirtualDiskParameters{ Version: 2, Version2: vhd.CreateVersion2{ - MaximumSize: sizeInGB * 1024 * 1024 * 1024, - BlockSizeInBytes: defaultVHDXBlockSizeInMB * 1024 * 1024, + MaximumSize: sizeInGB * memory.GiB, + BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB, }, } handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams) @@ -137,8 +138,8 @@ func SetupUtilityVMBaseLayer(ctx context.Context, uvmPath, baseVhdPath, diffVhdP createParams := &vhd.CreateVirtualDiskParameters{ Version: 2, Version2: vhd.CreateVersion2{ - MaximumSize: sizeInGB * 1024 * 1024 * 1024, - BlockSizeInBytes: defaultVHDXBlockSizeInMB * 1024 * 1024, + MaximumSize: sizeInGB * memory.GiB, + BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB, }, } handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams) diff --git a/test/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go b/test/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go index f952fbde87..0e42037d98 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go +++ b/test/vendor/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go @@ -14,14 +14,15 @@ import ( "github.com/pkg/errors" "github.com/Microsoft/hcsshim/ext4/internal/compactext4" + "github.com/Microsoft/hcsshim/internal/memory" ) const ( blockSize = compactext4.BlockSize // MerkleTreeBufioSize is a default buffer size to use with bufio.Reader - MerkleTreeBufioSize = 1024 * 1024 // 1MB + MerkleTreeBufioSize = memory.MiB // 1MB // RecommendedVHDSizeGB is the recommended size in GB for VHDs, which is not a hard limit. - RecommendedVHDSizeGB = 128 * 1024 * 1024 * 1024 + RecommendedVHDSizeGB = 128 * memory.GiB // VeritySignature is a value written to dm-verity super-block. VeritySignature = "verity" ) diff --git a/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go b/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go index e64d86eba4..504437270b 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go +++ b/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go @@ -13,6 +13,7 @@ import ( "time" "github.com/Microsoft/hcsshim/ext4/internal/format" + "github.com/Microsoft/hcsshim/internal/memory" ) // Writer writes a compact ext4 file system. @@ -101,15 +102,15 @@ const ( maxInodesPerGroup = BlockSize * 8 // Limited by the inode bitmap inodesPerGroupIncrement = BlockSize / inodeSize - defaultMaxDiskSize = 16 * 1024 * 1024 * 1024 // 16GB + defaultMaxDiskSize = 16 * memory.GiB // 16GB maxMaxDiskSize = 16 * 1024 * 1024 * 1024 * 1024 // 16TB groupDescriptorSize = 32 // Use the small group descriptor groupsPerDescriptorBlock = BlockSize / groupDescriptorSize - maxFileSize = 128 * 1024 * 1024 * 1024 // 128GB file size maximum for now - smallSymlinkSize = 59 // max symlink size that goes directly in the inode - maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent + maxFileSize = 128 * memory.GiB // 128GB file size maximum for now + smallSymlinkSize = 59 // max symlink size that goes directly in the inode + maxBlocksPerExtent = 0x8000 // maximum number of blocks in an extent inodeDataSize = 60 inodeUsedSize = 152 // fields through CrtimeExtra inodeExtraSize = inodeSize - inodeUsedSize diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/memory/pool.go b/test/vendor/github.com/Microsoft/hcsshim/internal/memory/pool.go index 6381dfd887..1ef5814d7e 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/memory/pool.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/memory/pool.go @@ -5,8 +5,8 @@ import ( ) const ( - minimumClassSize = MegaByte - maximumClassSize = 4 * GigaByte + minimumClassSize = MiB + maximumClassSize = 4 * GiB memoryClassNumber = 7 ) @@ -179,7 +179,7 @@ func (pa *PoolAllocator) findNextOffset(memCls classType) (classType, uint64, er continue } - target := maximumClassSize + target := uint64(maximumClassSize) for offset := range pi.free { if offset < target { target = offset diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/memory/types.go b/test/vendor/github.com/Microsoft/hcsshim/internal/memory/types.go index 7cf4368a95..d6cdb8cc4c 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/memory/types.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/memory/types.go @@ -5,8 +5,8 @@ import "github.com/pkg/errors" type classType uint32 const ( - MegaByte = uint64(1024 * 1024) - GigaByte = 1024 * MegaByte + MiB = 1024 * 1024 + GiB = 1024 * MiB ) var ( diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/constants.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/constants.go index 4bd84f26d2..aebd58a391 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/constants.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/constants.go @@ -3,6 +3,7 @@ package uvm import ( "errors" + "github.com/Microsoft/hcsshim/internal/memory" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" ) @@ -17,7 +18,7 @@ const ( // DefaultVPMemSizeBytes is the default size of a VPMem device if the create request // doesn't specify. - DefaultVPMemSizeBytes = 4 * 1024 * 1024 * 1024 // 4GB + DefaultVPMemSizeBytes = 4 * memory.GiB // 4GB ) var ( diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/memory_update.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/memory_update.go index 7a06f283dd..59444f417e 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/memory_update.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/memory_update.go @@ -8,18 +8,16 @@ import ( "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" + "github.com/Microsoft/hcsshim/internal/memory" ) -const ( - bytesPerPage = 4096 - bytesPerMB = 1024 * 1024 -) +const bytesPerPage = 4096 // UpdateMemory makes a call to the VM's orchestrator to update the VM's size in MB // Internally, HCS will get the number of pages this corresponds to and attempt to assign // pages to numa nodes evenly func (uvm *UtilityVM) UpdateMemory(ctx context.Context, sizeInBytes uint64) error { - requestedSizeInMB := sizeInBytes / bytesPerMB + requestedSizeInMB := sizeInBytes / memory.MiB actual := uvm.normalizeMemorySize(ctx, requestedSizeInMB) req := &hcsschema.ModifySettingRequest{ ResourcePath: resourcepaths.MemoryResourcePath, From 98519f22466ae73d3d7fdde81beaa84b79ce9ff3 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy <84944216+helsaawy@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:21:00 -0400 Subject: [PATCH 08/16] Linux GCS flags use 1 -, not 2 (#1358) Signed-off-by: Hamza El-Saawy --- internal/uvm/create_lcow.go | 4 ++-- .../github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index 79a065454e..596964a308 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -660,11 +660,11 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs } if opts.DisableTimeSyncService { - opts.ExecCommandLine = fmt.Sprintf("%s --disable-time-sync", opts.ExecCommandLine) + opts.ExecCommandLine = fmt.Sprintf("%s -disable-time-sync", opts.ExecCommandLine) } if log.IsScrubbingEnabled() { - opts.ExecCommandLine += " --scrub-logs" + opts.ExecCommandLine += " -scrub-logs" } execCmdArgs += " " + opts.ExecCommandLine diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go index 79a065454e..596964a308 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/create_lcow.go @@ -660,11 +660,11 @@ func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcs } if opts.DisableTimeSyncService { - opts.ExecCommandLine = fmt.Sprintf("%s --disable-time-sync", opts.ExecCommandLine) + opts.ExecCommandLine = fmt.Sprintf("%s -disable-time-sync", opts.ExecCommandLine) } if log.IsScrubbingEnabled() { - opts.ExecCommandLine += " --scrub-logs" + opts.ExecCommandLine += " -scrub-logs" } execCmdArgs += " " + opts.ExecCommandLine From db5e1b1743a3138bf211ad90eca7fdf23d0f326c Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy <84944216+helsaawy@users.noreply.github.com> Date: Wed, 20 Apr 2022 16:17:00 -0400 Subject: [PATCH 09/16] Splitting out GCS test and build (#1361) Signed-off-by: Hamza El-Saawy --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6762da403d..843fdbeddd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,6 +189,9 @@ jobs: with: go-version: '^1.17.0' + - name: Test + run: make test + - name: Pull busybox image run: docker pull busybox @@ -199,7 +202,5 @@ jobs: run: | docker export base_image_container | gzip > base.tar.gz - - name: Build And Test - run: | - BASE=./base.tar.gz - make all test \ No newline at end of file + - name: Build + run: make BASE=./base.tar.gz all From 51a69190b8f477b42265944f58ccff688b4caca7 Mon Sep 17 00:00:00 2001 From: Hamza El-Saawy <84944216+helsaawy@users.noreply.github.com> Date: Fri, 22 Apr 2022 11:46:27 -0400 Subject: [PATCH 10/16] spelling (#1365) Signed-off-by: Hamza El-Saawy --- cmd/containerd-shim-runhcs-v1/serve.go | 4 ++-- container.go | 2 +- hcn/hcnnamespace.go | 8 ++++---- internal/guest/runtime/hcsv2/process.go | 2 +- internal/lcow/scratch.go | 2 +- pkg/securitypolicy/securitypolicy.go | 2 +- pkg/securitypolicy/securitypolicyenforcer.go | 2 +- test/vendor/github.com/Microsoft/hcsshim/container.go | 2 +- .../github.com/Microsoft/hcsshim/hcn/hcnnamespace.go | 8 ++++---- .../github.com/Microsoft/hcsshim/internal/lcow/scratch.go | 2 +- .../hcsshim/pkg/securitypolicy/securitypolicy.go | 2 +- .../hcsshim/pkg/securitypolicy/securitypolicyenforcer.go | 2 +- 12 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/containerd-shim-runhcs-v1/serve.go b/cmd/containerd-shim-runhcs-v1/serve.go index 9b1213b087..b88c3a79c2 100644 --- a/cmd/containerd-shim-runhcs-v1/serve.go +++ b/cmd/containerd-shim-runhcs-v1/serve.go @@ -281,11 +281,11 @@ func readOptions(r io.Reader) (*runhcsopts.Options, error) { if len(d) > 0 { var a types.Any if err := proto.Unmarshal(d, &a); err != nil { - return nil, errors.Wrap(err, "failed unmarshaling into Any") + return nil, errors.Wrap(err, "failed unmarshalling into Any") } v, err := typeurl.UnmarshalAny(&a) if err != nil { - return nil, errors.Wrap(err, "failed unmarshaling by typeurl") + return nil, errors.Wrap(err, "failed unmarshalling by typeurl") } return v.(*runhcsopts.Options), nil } diff --git a/container.go b/container.go index 640e0b26f4..c8f09f88b9 100644 --- a/container.go +++ b/container.go @@ -62,7 +62,7 @@ type container struct { waitCh chan struct{} } -// createComputeSystemAdditionalJSON is read from the environment at initialisation +// createContainerAdditionalJSON is read from the environment at initialization // time. It allows an environment variable to define additional JSON which // is merged in the CreateComputeSystem call to HCS. var createContainerAdditionalJSON []byte diff --git a/hcn/hcnnamespace.go b/hcn/hcnnamespace.go index 7539e39fa8..44ba2fa1fd 100644 --- a/hcn/hcnnamespace.go +++ b/hcn/hcnnamespace.go @@ -296,11 +296,11 @@ func GetNamespaceContainerIds(namespaceId string) ([]string, error) { var containerIds []string for _, resource := range namespace.Resources { if resource.Type == "Container" { - var contaienrResource NamespaceResourceContainer - if err := json.Unmarshal([]byte(resource.Data), &contaienrResource); err != nil { + var containerResource NamespaceResourceContainer + if err := json.Unmarshal([]byte(resource.Data), &containerResource); err != nil { return nil, err } - containerIds = append(containerIds, contaienrResource.Id) + containerIds = append(containerIds, containerResource.Id) } } return containerIds, nil @@ -377,7 +377,7 @@ func (namespace *HostComputeNamespace) Sync() error { } shimPath := runhcs.VMPipePath(cfg.HostUniqueID) if err := runhcs.IssueVMRequest(shimPath, &req); err != nil { - // The shim is likey gone. Simply ignore the sync as if it didn't exist. + // The shim is likely gone. Simply ignore the sync as if it didn't exist. if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND { // Remove the reg key there is no point to try again _ = cfg.Remove() diff --git a/internal/guest/runtime/hcsv2/process.go b/internal/guest/runtime/hcsv2/process.go index 0a7dee8b30..e68a63070c 100644 --- a/internal/guest/runtime/hcsv2/process.go +++ b/internal/guest/runtime/hcsv2/process.go @@ -55,7 +55,7 @@ type containerProcess struct { // (runtime.Process).Wait() call returns, and exitCode has been updated. exitWg sync.WaitGroup - // Used to allow addtion/removal to the writersWg after an initial wait has + // Used to allow addition/removal to the writersWg after an initial wait has // already been issued. It is not safe to call Add/Done without holding this // lock. writersSyncRoot sync.Mutex diff --git a/internal/lcow/scratch.go b/internal/lcow/scratch.go index 001f3347cd..c86d141adf 100644 --- a/internal/lcow/scratch.go +++ b/internal/lcow/scratch.go @@ -32,7 +32,7 @@ const ( // requested size. It has a caching capability. If the cacheFile exists, and the // request is for a default size, a copy of that is made to the target. If the // size is non-default, or the cache file does not exist, it uses a utility VM -// to create target. It is the responsibility of the caller to synchronise +// to create target. It is the responsibility of the caller to synchronize // simultaneous attempts to create the cache file. func CreateScratch(ctx context.Context, lcowUVM *uvm.UtilityVM, destFile string, sizeGB uint32, cacheFile string) error { if lcowUVM == nil { diff --git a/pkg/securitypolicy/securitypolicy.go b/pkg/securitypolicy/securitypolicy.go index c9cdf3226f..5bd25a2d78 100644 --- a/pkg/securitypolicy/securitypolicy.go +++ b/pkg/securitypolicy/securitypolicy.go @@ -397,7 +397,7 @@ func newMountConstraints(mountConfigs []MountConfig) Mounts { } } -// Custom JSON marshalling to add `lenth` field that matches the number of +// Custom JSON marshalling to add `length` field that matches the number of // elements present in the `elements` field. func (c Containers) MarshalJSON() ([]byte, error) { diff --git a/pkg/securitypolicy/securitypolicyenforcer.go b/pkg/securitypolicy/securitypolicyenforcer.go index 5b17589838..f36ca7ac1f 100644 --- a/pkg/securitypolicy/securitypolicyenforcer.go +++ b/pkg/securitypolicy/securitypolicyenforcer.go @@ -161,7 +161,7 @@ var _ SecurityPolicyEnforcer = (*StandardSecurityPolicyEnforcer)(nil) func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, encoded string) *StandardSecurityPolicyEnforcer { // create new StandardSecurityPolicyEnforcer and add the expected containers // to it - // fill out corresponding devices structure by creating a "same shapped" + // fill out corresponding devices structure by creating a "same shaped" // devices listing that corresponds to our container root hash lists // the devices list will get filled out as layers are mounted devices := make([][]string, len(containers)) diff --git a/test/vendor/github.com/Microsoft/hcsshim/container.go b/test/vendor/github.com/Microsoft/hcsshim/container.go index 640e0b26f4..c8f09f88b9 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/container.go +++ b/test/vendor/github.com/Microsoft/hcsshim/container.go @@ -62,7 +62,7 @@ type container struct { waitCh chan struct{} } -// createComputeSystemAdditionalJSON is read from the environment at initialisation +// createContainerAdditionalJSON is read from the environment at initialization // time. It allows an environment variable to define additional JSON which // is merged in the CreateComputeSystem call to HCS. var createContainerAdditionalJSON []byte diff --git a/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go b/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go index 7539e39fa8..44ba2fa1fd 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go +++ b/test/vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go @@ -296,11 +296,11 @@ func GetNamespaceContainerIds(namespaceId string) ([]string, error) { var containerIds []string for _, resource := range namespace.Resources { if resource.Type == "Container" { - var contaienrResource NamespaceResourceContainer - if err := json.Unmarshal([]byte(resource.Data), &contaienrResource); err != nil { + var containerResource NamespaceResourceContainer + if err := json.Unmarshal([]byte(resource.Data), &containerResource); err != nil { return nil, err } - containerIds = append(containerIds, contaienrResource.Id) + containerIds = append(containerIds, containerResource.Id) } } return containerIds, nil @@ -377,7 +377,7 @@ func (namespace *HostComputeNamespace) Sync() error { } shimPath := runhcs.VMPipePath(cfg.HostUniqueID) if err := runhcs.IssueVMRequest(shimPath, &req); err != nil { - // The shim is likey gone. Simply ignore the sync as if it didn't exist. + // The shim is likely gone. Simply ignore the sync as if it didn't exist. if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND { // Remove the reg key there is no point to try again _ = cfg.Remove() diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go b/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go index 001f3347cd..c86d141adf 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/lcow/scratch.go @@ -32,7 +32,7 @@ const ( // requested size. It has a caching capability. If the cacheFile exists, and the // request is for a default size, a copy of that is made to the target. If the // size is non-default, or the cache file does not exist, it uses a utility VM -// to create target. It is the responsibility of the caller to synchronise +// to create target. It is the responsibility of the caller to synchronize // simultaneous attempts to create the cache file. func CreateScratch(ctx context.Context, lcowUVM *uvm.UtilityVM, destFile string, sizeGB uint32, cacheFile string) error { if lcowUVM == nil { diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go index c9cdf3226f..5bd25a2d78 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicy.go @@ -397,7 +397,7 @@ func newMountConstraints(mountConfigs []MountConfig) Mounts { } } -// Custom JSON marshalling to add `lenth` field that matches the number of +// Custom JSON marshalling to add `length` field that matches the number of // elements present in the `elements` field. func (c Containers) MarshalJSON() ([]byte, error) { diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go index 5b17589838..f36ca7ac1f 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go @@ -161,7 +161,7 @@ var _ SecurityPolicyEnforcer = (*StandardSecurityPolicyEnforcer)(nil) func NewStandardSecurityPolicyEnforcer(containers []securityPolicyContainer, encoded string) *StandardSecurityPolicyEnforcer { // create new StandardSecurityPolicyEnforcer and add the expected containers // to it - // fill out corresponding devices structure by creating a "same shapped" + // fill out corresponding devices structure by creating a "same shaped" // devices listing that corresponds to our container root hash lists // the devices list will get filled out as layers are mounted devices := make([][]string, len(containers)) From 8e6c081423e3b9dda3c30c58b736edfe1b74a4f8 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Fri, 22 Apr 2022 10:31:59 -0700 Subject: [PATCH 11/16] Revert "Fix working_dir negative test error expectation (#1348)" (#1368) This reverts commit 2028de8b8d5e0516e0e65664f9085dab02a6a5e2. During local testing a gcs with an older version of security policy was used when doing the fix: #1322. As we can see, the quotations weren't there. However, later a PR was merged, which added them: #1311 Signed-off-by: Maksim An --- test/cri-containerd/policy_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cri-containerd/policy_test.go b/test/cri-containerd/policy_test.go index 8ab5a6e6e3..9d1348218d 100644 --- a/test/cri-containerd/policy_test.go +++ b/test/cri-containerd/policy_test.go @@ -382,7 +382,7 @@ func Test_RunContainer_InvalidContainerConfigs_NotAllowed(t *testing.T) { req.Config.WorkingDir = "/non/existent" return nil }, - expectedError: "working_dir /non/existent unmatched by policy rule", + expectedError: "working_dir \"/non/existent\" unmatched by policy rule", }, { name: "InvalidCommand", From 57bff8854d1a52a92315d3389de5a95b07694206 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Fri, 22 Apr 2022 10:33:04 -0700 Subject: [PATCH 12/16] Change receivers and returns for security policy enforcers (#1369) Signed-off-by: Maksim An --- .../mountmonitoringsecuritypolicyenforcer.go | 10 +++++----- pkg/securitypolicy/securitypolicyenforcer.go | 20 +++++++++---------- .../securitypolicy/securitypolicyenforcer.go | 20 +++++++++---------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go b/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go index 5b088d5311..768d92721f 100644 --- a/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go +++ b/internal/guest/storage/test/policy/mountmonitoringsecuritypolicyenforcer.go @@ -20,22 +20,22 @@ type MountMonitoringSecurityPolicyEnforcer struct { var _ securitypolicy.SecurityPolicyEnforcer = (*MountMonitoringSecurityPolicyEnforcer)(nil) -func (p *MountMonitoringSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { +func (p *MountMonitoringSecurityPolicyEnforcer) EnforceDeviceMountPolicy(_ string, _ string) error { p.DeviceMountCalls++ return nil } -func (p *MountMonitoringSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(target string) (err error) { +func (p *MountMonitoringSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(_ string) error { p.DeviceUnmountCalls++ return nil } -func (p *MountMonitoringSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) { +func (p *MountMonitoringSecurityPolicyEnforcer) EnforceOverlayMountPolicy(_ string, _ []string) error { p.OverlayMountCalls++ return nil } -func (p *MountMonitoringSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { +func (MountMonitoringSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) error { return nil } @@ -43,7 +43,7 @@ func (MountMonitoringSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ * return nil } -func (p *MountMonitoringSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { +func (MountMonitoringSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return nil } diff --git a/pkg/securitypolicy/securitypolicyenforcer.go b/pkg/securitypolicy/securitypolicyenforcer.go index f36ca7ac1f..d2904848f5 100644 --- a/pkg/securitypolicy/securitypolicyenforcer.go +++ b/pkg/securitypolicy/securitypolicyenforcer.go @@ -814,19 +814,19 @@ type OpenDoorSecurityPolicyEnforcer struct{} var _ SecurityPolicyEnforcer = (*OpenDoorSecurityPolicyEnforcer)(nil) -func (p *OpenDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(_ string, _ string) error { return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(target string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(_ string) error { return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(_ string, _ []string) error { return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) error { return nil } @@ -834,7 +834,7 @@ func (OpenDoorSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spe return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { +func (OpenDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return nil } @@ -846,23 +846,23 @@ type ClosedDoorSecurityPolicyEnforcer struct{} var _ SecurityPolicyEnforcer = (*ClosedDoorSecurityPolicyEnforcer)(nil) -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(_ string, _ string) error { return errors.New("mounting is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(target string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(_ string) error { return errors.New("unmounting is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(_ string, _ []string) error { return errors.New("creating an overlay fs is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) error { return errors.New("running commands is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { +func (ClosedDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return errors.New("enforcing expected mounts is denied by policy") } diff --git a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go index f36ca7ac1f..d2904848f5 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go +++ b/test/vendor/github.com/Microsoft/hcsshim/pkg/securitypolicy/securitypolicyenforcer.go @@ -814,19 +814,19 @@ type OpenDoorSecurityPolicyEnforcer struct{} var _ SecurityPolicyEnforcer = (*OpenDoorSecurityPolicyEnforcer)(nil) -func (p *OpenDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(_ string, _ string) error { return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(target string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(_ string) error { return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(_ string, _ []string) error { return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { +func (OpenDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) error { return nil } @@ -834,7 +834,7 @@ func (OpenDoorSecurityPolicyEnforcer) EnforceMountPolicy(_, _ string, _ *oci.Spe return nil } -func (p *OpenDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { +func (OpenDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return nil } @@ -846,23 +846,23 @@ type ClosedDoorSecurityPolicyEnforcer struct{} var _ SecurityPolicyEnforcer = (*ClosedDoorSecurityPolicyEnforcer)(nil) -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(target string, deviceHash string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceDeviceMountPolicy(_ string, _ string) error { return errors.New("mounting is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(target string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceDeviceUnmountPolicy(_ string) error { return errors.New("unmounting is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(containerID string, layerPaths []string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceOverlayMountPolicy(_ string, _ []string) error { return errors.New("creating an overlay fs is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) (err error) { +func (ClosedDoorSecurityPolicyEnforcer) EnforceCreateContainerPolicy(_ string, _ []string, _ []string, _ string) error { return errors.New("running commands is denied by policy") } -func (p *ClosedDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { +func (ClosedDoorSecurityPolicyEnforcer) EnforceExpectedMountsPolicy(_ string, _ *oci.Spec) error { return errors.New("enforcing expected mounts is denied by policy") } From 4a33ed55759ea267f3ebf4d33fef5a1d29f0c45a Mon Sep 17 00:00:00 2001 From: Maksim An Date: Fri, 22 Apr 2022 15:04:50 -0700 Subject: [PATCH 13/16] Allow multiple CreateContainer operations at the same time. (#1355) Prior to this change, GCS allowed only one CreateContainer operation at a time. This isn't an issue in general case, however this doesn't work properly with synchronization via OCI runtime hook. Synchronization via runtime hook was introduced in: https://github.com/microsoft/hcsshim/pull/1258 It injects a `CreateRuntime` OCI hook, if security policy provides wait paths. This allows container-A to run after container-B, where container-B writes to an empty directory volume shared between the two containers to signal that it's done some setup container-A depends on. In general case, container-A can be started before container-B which results in a deadlock, because `CreateContainer` request holds a lock to a map, which keeps track of running containers. To resolve the issue, the code has been updated to do a more granular locking when reading/updating the containers map: - Add a new "status" field to Container object and atomic setter/getter, which can be either "Created" or "Creating". New `uint32` type alias and constants were added to represent the values (`containerCreated` and `containerCreating`) - Remove locking from `CreateContainer` function - Rework `GetContainer` to `GetCreatedContainer`, which returns the container object only when it's in `containerCreated` state, otherwise either `gcserr.HrVmcomputeSystemNotFound` or `gcserr.HrVmcomputeInvalidState` error returned. - Add new `AddContainer(id, container)` function, which updates the containers map with new container instances. - Rework `CreateContainer` to initially add new container objects into the containers map and set the "status" to `containerCreating` at the start of the function and set it to `containerCreated` only when the container is successfully created in runtime. Reworking `GetContainer` to `GetCreatedContainer` seemed to be the least invasive change, which allows us to limit updates in the affected places. If `GetContainer` is left unchanged, then handling of containers in status "Creating" needs to take place and this requires handling cases when (e.g.) a modification request is sent to a container which isn't yet running. Additionally update synchronization CRI tests to use go routines to properly reproduce the scenario. Signed-off-by: Maksim An --- internal/guest/bridge/bridge_v2.go | 14 ++--- internal/guest/runtime/hcsv2/container.go | 25 +++++++++ internal/guest/runtime/hcsv2/uvm.go | 63 ++++++++++++++--------- test/cri-containerd/policy_test.go | 32 +++++++++--- 4 files changed, 95 insertions(+), 39 deletions(-) diff --git a/internal/guest/bridge/bridge_v2.go b/internal/guest/bridge/bridge_v2.go index f7924a2576..99777ca84a 100644 --- a/internal/guest/bridge/bridge_v2.go +++ b/internal/guest/bridge/bridge_v2.go @@ -199,7 +199,7 @@ func (b *Bridge) execProcessV2(r *Request) (_ RequestResponse, err error) { var c *hcsv2.Container if params.IsExternal || request.ContainerID == hcsv2.UVMContainerID { pid, err = b.hostState.RunExternalProcess(ctx, params, conSettings) - } else if c, err = b.hostState.GetContainer(request.ContainerID); err == nil { + } else if c, err = b.hostState.GetCreatedContainer(request.ContainerID); err == nil { // We found a V2 container. Treat this as a V2 process. if params.OCIProcess == nil { pid, err = c.Start(ctx, conSettings) @@ -267,7 +267,7 @@ func (b *Bridge) signalContainerV2(ctx context.Context, span *trace.Span, r *Req b.quitChan <- true b.hostState.Shutdown() } else { - c, err := b.hostState.GetContainer(request.ContainerID) + c, err := b.hostState.GetCreatedContainer(request.ContainerID) if err != nil { return nil, err } @@ -296,7 +296,7 @@ func (b *Bridge) signalProcessV2(r *Request) (_ RequestResponse, err error) { trace.Int64Attribute("pid", int64(request.ProcessID)), trace.Int64Attribute("signal", int64(request.Options.Signal))) - c, err := b.hostState.GetContainer(request.ContainerID) + c, err := b.hostState.GetCreatedContainer(request.ContainerID) if err != nil { return nil, err } @@ -344,7 +344,7 @@ func (b *Bridge) getPropertiesV2(r *Request) (_ RequestResponse, err error) { return nil, errors.New("getPropertiesV2 is not supported against the UVM") } - c, err := b.hostState.GetContainer(request.ContainerID) + c, err := b.hostState.GetCreatedContainer(request.ContainerID) if err != nil { return nil, err } @@ -407,7 +407,7 @@ func (b *Bridge) waitOnProcessV2(r *Request) (_ RequestResponse, err error) { } exitCodeChan, doneChan = p.Wait() } else { - c, err := b.hostState.GetContainer(request.ContainerID) + c, err := b.hostState.GetCreatedContainer(request.ContainerID) if err != nil { return nil, err } @@ -453,7 +453,7 @@ func (b *Bridge) resizeConsoleV2(r *Request) (_ RequestResponse, err error) { trace.Int64Attribute("height", int64(request.Height)), trace.Int64Attribute("width", int64(request.Width))) - c, err := b.hostState.GetContainer(request.ContainerID) + c, err := b.hostState.GetCreatedContainer(request.ContainerID) if err != nil { return nil, err } @@ -514,7 +514,7 @@ func (b *Bridge) deleteContainerStateV2(r *Request) (_ RequestResponse, err erro return nil, errors.Wrapf(err, "failed to unmarshal JSON in message \"%s\"", r.Message) } - c, err := b.hostState.GetContainer(request.ContainerID) + c, err := b.hostState.GetCreatedContainer(request.ContainerID) if err != nil { return nil, err } diff --git a/internal/guest/runtime/hcsv2/container.go b/internal/guest/runtime/hcsv2/container.go index d890d0c2b0..cc4304c2f3 100644 --- a/internal/guest/runtime/hcsv2/container.go +++ b/internal/guest/runtime/hcsv2/container.go @@ -6,6 +6,7 @@ package hcsv2 import ( "context" "sync" + "sync/atomic" "syscall" "github.com/containerd/cgroups" @@ -28,6 +29,18 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" ) +// containerStatus has been introduced to enable parallel container creation +type containerStatus uint32 + +const ( + // containerCreating is the default status set on a Container object, when + // no underlying runtime container or init process has been assigned + containerCreating containerStatus = iota + // containerCreated is the status when a runtime container and init process + // have been assigned, but runtime start command has not been issued yet + containerCreated +) + type Container struct { id string vsock transport.Transport @@ -43,6 +56,9 @@ type Container struct { processesMutex sync.Mutex processes map[uint32]*containerProcess + + // Only access atomically through getStatus/setStatus. + status containerStatus } func (c *Container) Start(ctx context.Context, conSettings stdio.ConnectionSettings) (int, error) { @@ -220,3 +236,12 @@ func (c *Container) GetStats(ctx context.Context) (*v1.Metrics, error) { func (c *Container) modifyContainerConstraints(ctx context.Context, rt guestrequest.RequestType, cc *guestresource.LCOWContainerConstraints) (err error) { return c.Update(ctx, cc.Linux) } + +func (c *Container) getStatus() containerStatus { + val := atomic.LoadUint32((*uint32)(&c.status)) + return containerStatus(val) +} + +func (c *Container) setStatus(st containerStatus) { + atomic.StoreUint32((*uint32)(&c.status), uint32(st)) +} diff --git a/internal/guest/runtime/hcsv2/uvm.go b/internal/guest/runtime/hcsv2/uvm.go index 568f0ed448..380112a00d 100644 --- a/internal/guest/runtime/hcsv2/uvm.go +++ b/internal/guest/runtime/hcsv2/uvm.go @@ -16,11 +16,8 @@ import ( "syscall" "time" - "github.com/Microsoft/hcsshim/internal/guest/policy" - "github.com/mattn/go-shellwords" - "github.com/pkg/errors" - "github.com/Microsoft/hcsshim/internal/guest/gcserr" + "github.com/Microsoft/hcsshim/internal/guest/policy" "github.com/Microsoft/hcsshim/internal/guest/prot" "github.com/Microsoft/hcsshim/internal/guest/runtime" "github.com/Microsoft/hcsshim/internal/guest/spec" @@ -36,6 +33,8 @@ import ( "github.com/Microsoft/hcsshim/internal/protocol/guestresource" "github.com/Microsoft/hcsshim/pkg/annotations" "github.com/Microsoft/hcsshim/pkg/securitypolicy" + "github.com/mattn/go-shellwords" + "github.com/pkg/errors" ) // UVMContainerID is the ContainerID that will be sent on any prot.MessageBase @@ -123,19 +122,30 @@ func (h *Host) RemoveContainer(id string) { delete(h.containers, id) } -func (h *Host) getContainerLocked(id string) (*Container, error) { +func (h *Host) GetCreatedContainer(id string) (*Container, error) { + h.containersMutex.Lock() + defer h.containersMutex.Unlock() + if c, ok := h.containers[id]; !ok { return nil, gcserr.NewHresultError(gcserr.HrVmcomputeSystemNotFound) } else { + if c.getStatus() != containerCreated { + return nil, fmt.Errorf("container is not in state \"created\": %w", + gcserr.NewHresultError(gcserr.HrVmcomputeInvalidState)) + } return c, nil } } -func (h *Host) GetContainer(id string) (*Container, error) { +func (h *Host) AddContainer(id string, c *Container) error { h.containersMutex.Lock() defer h.containersMutex.Unlock() - return h.getContainerLocked(id) + if _, ok := h.containers[id]; ok { + return gcserr.NewHresultError(gcserr.HrVmcomputeSystemAlreadyExists) + } + h.containers[id] = c + return nil } func setupSandboxMountsPath(id string) (err error) { @@ -162,12 +172,25 @@ func setupSandboxHugePageMountsPath(id string) error { } func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VMHostedContainerSettingsV2) (_ *Container, err error) { - h.containersMutex.Lock() - defer h.containersMutex.Unlock() + criType, isCRI := settings.OCISpecification.Annotations[annotations.KubernetesContainerType] + c := &Container{ + id: id, + vsock: h.vsock, + spec: settings.OCISpecification, + isSandbox: criType == "sandbox", + exitType: prot.NtUnexpectedExit, + processes: make(map[uint32]*containerProcess), + status: containerCreating, + } - if _, ok := h.containers[id]; ok { - return nil, gcserr.NewHresultError(gcserr.HrVmcomputeSystemAlreadyExists) + if err := h.AddContainer(id, c); err != nil { + return nil, err } + defer func() { + if err != nil { + h.RemoveContainer(id) + } + }() err = h.securityPolicyEnforcer.EnforceCreateContainerPolicy( id, @@ -175,13 +198,11 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM settings.OCISpecification.Process.Env, settings.OCISpecification.Process.Cwd, ) - if err != nil { return nil, errors.Wrapf(err, "container creation denied due to policy") } var namespaceID string - criType, isCRI := settings.OCISpecification.Annotations[annotations.KubernetesContainerType] // for sandbox container sandboxID is same as container id sandboxID := id if isCRI { @@ -290,15 +311,7 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM return nil, errors.Wrapf(err, "failed to get container init process") } - c := &Container{ - id: id, - vsock: h.vsock, - spec: settings.OCISpecification, - isSandbox: criType == "sandbox", - container: con, - exitType: prot.NtUnexpectedExit, - processes: make(map[uint32]*containerProcess), - } + c.container = con c.initProcess = newProcess(c, settings.OCISpecification.Process, init, uint32(c.container.Pid()), true) // Sandbox or standalone, move the networks to the container namespace @@ -318,7 +331,7 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM } } - h.containers[id] = c + c.setStatus(containerCreated) return c, nil } @@ -337,7 +350,7 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, req * case guestresource.ResourceTypeVPCIDevice: return modifyMappedVPCIDevice(ctx, req.RequestType, req.Settings.(*guestresource.LCOWMappedVPCIDevice)) case guestresource.ResourceTypeContainerConstraints: - c, err := h.GetContainer(containerID) + c, err := h.GetCreatedContainer(containerID) if err != nil { return err } @@ -355,7 +368,7 @@ func (h *Host) modifyHostSettings(ctx context.Context, containerID string, req * } func (h *Host) modifyContainerSettings(ctx context.Context, containerID string, req *guestrequest.ModificationRequest) error { - c, err := h.GetContainer(containerID) + c, err := h.GetCreatedContainer(containerID) if err != nil { return err } diff --git a/test/cri-containerd/policy_test.go b/test/cri-containerd/policy_test.go index 9d1348218d..2892c774e4 100644 --- a/test/cri-containerd/policy_test.go +++ b/test/cri-containerd/policy_test.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" "testing" + "time" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" @@ -232,13 +233,30 @@ func Test_RunContainers_WithSyncHooks_ValidWaitPath(t *testing.T) { cidWriter := createContainer(t, client, ctx, writerReq) cidWaiter := createContainer(t, client, ctx, waiterReq) - startContainer(t, client, ctx, cidWriter) - defer removeContainer(t, client, ctx, cidWriter) - defer stopContainer(t, client, ctx, cidWriter) - - startContainer(t, client, ctx, cidWaiter) - defer removeContainer(t, client, ctx, cidWaiter) - defer stopContainer(t, client, ctx, cidWaiter) + errChan := make(chan error) + go func() { + _, err := client.StartContainer(ctx, &runtime.StartContainerRequest{ContainerId: cidWaiter}) + errChan <- err + defer removeContainer(t, client, ctx, cidWaiter) + defer stopContainer(t, client, ctx, cidWaiter) + }() + + // give some time for the first go routine to kick in. + time.Sleep(time.Second) + + go func() { + _, err := client.StartContainer(ctx, &runtime.StartContainerRequest{ContainerId: cidWriter}) + errChan <- err + defer removeContainer(t, client, ctx, cidWriter) + defer stopContainer(t, client, ctx, cidWriter) + }() + + for i := 0; i < 2; i++ { + if err := <-errChan; err != nil { + close(errChan) + t.Fatalf("failed to start container: %s", err) + } + } } func Test_RunContainers_WithSyncHooks_InvalidWaitPath(t *testing.T) { From fb706b397132bb9c5a44fcde461c00beb0af5cb7 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Wed, 6 Apr 2022 18:28:15 -0700 Subject: [PATCH 14/16] Port grantvmgroupaccess code from go-winio repo for further extension Signed-off-by: Maksim An --- computestorage/helpers.go | 3 +- .../security/grantvmgroupaccess.go | 15 +- internal/security/grantvmgroupaccess_test.go | 120 +++ internal/security/mksyscall_windows.go | 958 ++++++++++++++++++ .../security/syscall_windows.go | 6 +- .../security/zsyscall_windows.go | 24 +- internal/tools/grantvmgroupaccess/main.go | 2 +- internal/uvm/scsi.go | 2 +- .../hcsshim/computestorage/helpers.go | 3 +- .../internal}/security/grantvmgroupaccess.go | 15 +- .../internal}/security/syscall_windows.go | 6 +- .../internal}/security/zsyscall_windows.go | 24 +- .../Microsoft/hcsshim/internal/uvm/scsi.go | 2 +- test/vendor/modules.txt | 2 +- vendor/golang.org/x/sys/execabs/execabs.go | 102 ++ vendor/modules.txt | 2 +- 16 files changed, 1233 insertions(+), 53 deletions(-) rename {test/vendor/github.com/Microsoft/go-winio/pkg => internal}/security/grantvmgroupaccess.go (90%) create mode 100644 internal/security/grantvmgroupaccess_test.go create mode 100644 internal/security/mksyscall_windows.go rename {vendor/github.com/Microsoft/go-winio/pkg => internal}/security/syscall_windows.go (61%) rename {vendor/github.com/Microsoft/go-winio/pkg => internal}/security/zsyscall_windows.go (59%) rename {vendor/github.com/Microsoft/go-winio/pkg => test/vendor/github.com/Microsoft/hcsshim/internal}/security/grantvmgroupaccess.go (90%) rename test/vendor/github.com/Microsoft/{go-winio/pkg => hcsshim/internal}/security/syscall_windows.go (61%) rename test/vendor/github.com/Microsoft/{go-winio/pkg => hcsshim/internal}/security/zsyscall_windows.go (59%) create mode 100644 vendor/golang.org/x/sys/execabs/execabs.go diff --git a/computestorage/helpers.go b/computestorage/helpers.go index a0e329edec..c3608dcec8 100644 --- a/computestorage/helpers.go +++ b/computestorage/helpers.go @@ -8,11 +8,12 @@ import ( "path/filepath" "syscall" - "github.com/Microsoft/go-winio/pkg/security" "github.com/Microsoft/go-winio/vhd" "github.com/Microsoft/hcsshim/internal/memory" "github.com/pkg/errors" "golang.org/x/sys/windows" + + "github.com/Microsoft/hcsshim/internal/security" ) const defaultVHDXBlockSizeInMB = 1 diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go b/internal/security/grantvmgroupaccess.go similarity index 90% rename from test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go rename to internal/security/grantvmgroupaccess.go index fca241590c..602920786c 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go +++ b/internal/security/grantvmgroupaccess.go @@ -3,11 +3,10 @@ package security import ( + "fmt" "os" "syscall" "unsafe" - - "github.com/pkg/errors" ) type ( @@ -72,7 +71,7 @@ func GrantVmGroupAccess(name string) error { // Stat (to determine if `name` is a directory). s, err := os.Stat(name) if err != nil { - return errors.Wrapf(err, "%s os.Stat %s", gvmga, name) + return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) } // Get a handle to the file/directory. Must defer Close on success. @@ -88,7 +87,7 @@ func GrantVmGroupAccess(name string) error { sd := uintptr(0) origDACL := uintptr(0) if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { - return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) @@ -102,7 +101,7 @@ func GrantVmGroupAccess(name string) error { // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { - return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) } return nil @@ -120,7 +119,7 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { } fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name) + return 0, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) } return fd, nil } @@ -131,7 +130,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp // Generate pointers to the SIDs based on the string SIDs sid, err := syscall.StringToSid(sidVmGroup) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup) + return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVmGroup, err) } inheritance := inheritModeNoInheritance @@ -154,7 +153,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp modifiedDACL := uintptr(0) if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { - return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name) + return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) } return modifiedDACL, nil diff --git a/internal/security/grantvmgroupaccess_test.go b/internal/security/grantvmgroupaccess_test.go new file mode 100644 index 0000000000..2dffaa1284 --- /dev/null +++ b/internal/security/grantvmgroupaccess_test.go @@ -0,0 +1,120 @@ +//+build windows + +package security + +import ( + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + exec "golang.org/x/sys/execabs" +) + +const ( + vmAccountName = `NT VIRTUAL MACHINE\\Virtual Machines` + vmAccountSID = "S-1-5-83-0" +) + +// TestGrantVmGroupAccess verifies for the three case of a file, a directory, +// and a file in a directory that the appropriate ACEs are set, including +// inheritance in the second two examples. These are the expected ACES. Is +// verified by running icacls and comparing output. +// +// File: +// S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(R,W) +// S-1-5-83-1-3166535780-1122986932-343720105-43916321:(R,W) +// +// Directory: +// S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(OI)(CI)(R,W) +// S-1-5-83-1-3166535780-1122986932-343720105-43916321:(OI)(CI)(R,W) +// +// File in directory (inherited): +// S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(I)(R,W) +// S-1-5-83-1-3166535780-1122986932-343720105-43916321:(I)(R,W) + +func TestGrantVmGroupAccess(t *testing.T) { + f, err := ioutil.TempFile("", "gvmgafile") + if err != nil { + t.Fatal(err) + } + defer func() { + f.Close() + os.Remove(f.Name()) + }() + + d, err := ioutil.TempDir("", "gvmgadir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(d) + + find, err := os.Create(filepath.Join(d, "find.txt")) + if err != nil { + t.Fatal(err) + } + + if err := GrantVmGroupAccess(f.Name()); err != nil { + t.Fatal(err) + } + + if err := GrantVmGroupAccess(d); err != nil { + t.Fatal(err) + } + + verifyVMAccountDACLs(t, + f.Name(), + []string{`(R)`}, + ) + + // Two items here: + // - One explicit read only. + // - Other applies to this folder, subfolders and files + // (OI): object inherit + // (CI): container inherit + // (IO): inherit only + // (GR): generic read + // + // In properties for the directory, advanced security settings, this will + // show as a single line "Allow/Virtual Machines/Read/Inherited from none/This folder, subfolder and files + verifyVMAccountDACLs(t, + d, + []string{`(R)`, `(OI)(CI)(IO)(GR)`}, + ) + + verifyVMAccountDACLs(t, + find.Name(), + []string{`(I)(R)`}, + ) + +} + +func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { + cmd := exec.Command("icacls", name) + outb, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + out := string(outb) + + for _, p := range permissions { + // Avoid '(' and ')' being part of match groups + p = strings.Replace(p, "(", "\\(", -1) + p = strings.Replace(p, ")", "\\)", -1) + + nameToCheck := vmAccountName + ":" + p + sidToCheck := vmAccountSID + ":" + p + + rxName := regexp.MustCompile(nameToCheck) + rxSID := regexp.MustCompile(sidToCheck) + + matchesName := rxName.FindAllStringIndex(out, -1) + matchesSID := rxSID.FindAllStringIndex(out, -1) + + if len(matchesName) != 1 && len(matchesSID) != 1 { + t.Fatalf("expected one match for %s or %s\n%s", nameToCheck, sidToCheck, out) + } + } +} diff --git a/internal/security/mksyscall_windows.go b/internal/security/mksyscall_windows.go new file mode 100644 index 0000000000..d52b6137c7 --- /dev/null +++ b/internal/security/mksyscall_windows.go @@ -0,0 +1,958 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Hard-coding unicode mode for VHD library. + +// +build ignore + +/* +mksyscall_windows generates windows system call bodies + +It parses all files specified on command line containing function +prototypes (like syscall_windows.go) and prints system call bodies +to standard output. + +The prototypes are marked by lines beginning with "//sys" and read +like func declarations if //sys is replaced by func, but: + +* The parameter lists must give a name for each argument. This + includes return parameters. + +* The parameter lists must give a type for each argument: + the (x, y, z int) shorthand is not allowed. + +* If the return parameter is an error number, it must be named err. + +* If go func name needs to be different from its winapi dll name, + the winapi name could be specified at the end, after "=" sign, like + //sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA + +* Each function that returns err needs to supply a condition, that + return value of winapi will be tested against to detect failure. + This would set err to windows "last-error", otherwise it will be nil. + The value can be provided at end of //sys declaration, like + //sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA + and is [failretval==0] by default. + +* If the function name ends in a "?", then the function not existing is non- + fatal, and an error will be returned instead of panicking. + +Usage: + mksyscall_windows [flags] [path ...] + +The flags are: + -output + Specify output file name (outputs to console if blank). + -trace + Generate print statement after every syscall. +*/ +package main + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "go/format" + "go/parser" + "go/token" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "text/template" +) + +var ( + filename = flag.String("output", "", "output file name (standard output if omitted)") + printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall") + systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory") +) + +func trim(s string) string { + return strings.Trim(s, " \t") +} + +var packageName string + +func packagename() string { + return packageName +} + +func syscalldot() string { + if packageName == "syscall" { + return "" + } + return "syscall." +} + +// Param is function parameter +type Param struct { + Name string + Type string + fn *Fn + tmpVarIdx int +} + +// tmpVar returns temp variable name that will be used to represent p during syscall. +func (p *Param) tmpVar() string { + if p.tmpVarIdx < 0 { + p.tmpVarIdx = p.fn.curTmpVarIdx + p.fn.curTmpVarIdx++ + } + return fmt.Sprintf("_p%d", p.tmpVarIdx) +} + +// BoolTmpVarCode returns source code for bool temp variable. +func (p *Param) BoolTmpVarCode() string { + const code = `var %[1]s uint32 + if %[2]s { + %[1]s = 1 + }` + return fmt.Sprintf(code, p.tmpVar(), p.Name) +} + +// BoolPointerTmpVarCode returns source code for bool temp variable. +func (p *Param) BoolPointerTmpVarCode() string { + const code = `var %[1]s uint32 + if *%[2]s { + %[1]s = 1 + }` + return fmt.Sprintf(code, p.tmpVar(), p.Name) +} + +// SliceTmpVarCode returns source code for slice temp variable. +func (p *Param) SliceTmpVarCode() string { + const code = `var %s *%s + if len(%s) > 0 { + %s = &%s[0] + }` + tmp := p.tmpVar() + return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name) +} + +// StringTmpVarCode returns source code for string temp variable. +func (p *Param) StringTmpVarCode() string { + errvar := p.fn.Rets.ErrorVarName() + if errvar == "" { + errvar = "_" + } + tmp := p.tmpVar() + const code = `var %s %s + %s, %s = %s(%s)` + s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name) + if errvar == "-" { + return s + } + const morecode = ` + if %s != nil { + return + }` + return s + fmt.Sprintf(morecode, errvar) +} + +// TmpVarCode returns source code for temp variable. +func (p *Param) TmpVarCode() string { + switch { + case p.Type == "bool": + return p.BoolTmpVarCode() + case p.Type == "*bool": + return p.BoolPointerTmpVarCode() + case strings.HasPrefix(p.Type, "[]"): + return p.SliceTmpVarCode() + default: + return "" + } +} + +// TmpVarReadbackCode returns source code for reading back the temp variable into the original variable. +func (p *Param) TmpVarReadbackCode() string { + switch { + case p.Type == "*bool": + return fmt.Sprintf("*%s = %s != 0", p.Name, p.tmpVar()) + default: + return "" + } +} + +// TmpVarHelperCode returns source code for helper's temp variable. +func (p *Param) TmpVarHelperCode() string { + if p.Type != "string" { + return "" + } + return p.StringTmpVarCode() +} + +// SyscallArgList returns source code fragments representing p parameter +// in syscall. Slices are translated into 2 syscall parameters: pointer to +// the first element and length. +func (p *Param) SyscallArgList() []string { + t := p.HelperType() + var s string + switch { + case t == "*bool": + s = fmt.Sprintf("unsafe.Pointer(&%s)", p.tmpVar()) + case t[0] == '*': + s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name) + case t == "bool": + s = p.tmpVar() + case strings.HasPrefix(t, "[]"): + return []string{ + fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()), + fmt.Sprintf("uintptr(len(%s))", p.Name), + } + default: + s = p.Name + } + return []string{fmt.Sprintf("uintptr(%s)", s)} +} + +// IsError determines if p parameter is used to return error. +func (p *Param) IsError() bool { + return p.Name == "err" && p.Type == "error" +} + +// HelperType returns type of parameter p used in helper function. +func (p *Param) HelperType() string { + if p.Type == "string" { + return p.fn.StrconvType() + } + return p.Type +} + +// join concatenates parameters ps into a string with sep separator. +// Each parameter is converted into string by applying fn to it +// before conversion. +func join(ps []*Param, fn func(*Param) string, sep string) string { + if len(ps) == 0 { + return "" + } + a := make([]string, 0) + for _, p := range ps { + a = append(a, fn(p)) + } + return strings.Join(a, sep) +} + +// Rets describes function return parameters. +type Rets struct { + Name string + Type string + ReturnsError bool + FailCond string + fnMaybeAbsent bool +} + +// ErrorVarName returns error variable name for r. +func (r *Rets) ErrorVarName() string { + if r.ReturnsError { + return "err" + } + if r.Type == "error" { + return r.Name + } + return "" +} + +// ToParams converts r into slice of *Param. +func (r *Rets) ToParams() []*Param { + ps := make([]*Param, 0) + if len(r.Name) > 0 { + ps = append(ps, &Param{Name: r.Name, Type: r.Type}) + } + if r.ReturnsError { + ps = append(ps, &Param{Name: "err", Type: "error"}) + } + return ps +} + +// List returns source code of syscall return parameters. +func (r *Rets) List() string { + s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ") + if len(s) > 0 { + s = "(" + s + ")" + } else if r.fnMaybeAbsent { + s = "(err error)" + } + return s +} + +// PrintList returns source code of trace printing part correspondent +// to syscall return values. +func (r *Rets) PrintList() string { + return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) +} + +// SetReturnValuesCode returns source code that accepts syscall return values. +func (r *Rets) SetReturnValuesCode() string { + if r.Name == "" && !r.ReturnsError { + return "" + } + retvar := "r0" + if r.Name == "" { + retvar = "r1" + } + errvar := "_" + if r.ReturnsError { + errvar = "e1" + } + return fmt.Sprintf("%s, _, %s := ", retvar, errvar) +} + +func (r *Rets) useLongHandleErrorCode(retvar string) string { + const code = `if %s { + err = errnoErr(e1) + }` + cond := retvar + " == 0" + if r.FailCond != "" { + cond = strings.Replace(r.FailCond, "failretval", retvar, 1) + } + return fmt.Sprintf(code, cond) +} + +// SetErrorCode returns source code that sets return parameters. +func (r *Rets) SetErrorCode() string { + const code = `if r0 != 0 { + %s = %sErrno(r0) + }` + if r.Name == "" && !r.ReturnsError { + return "" + } + if r.Name == "" { + return r.useLongHandleErrorCode("r1") + } + if r.Type == "error" { + return fmt.Sprintf(code, r.Name, syscalldot()) + } + s := "" + switch { + case r.Type[0] == '*': + s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type) + case r.Type == "bool": + s = fmt.Sprintf("%s = r0 != 0", r.Name) + default: + s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type) + } + if !r.ReturnsError { + return s + } + return s + "\n\t" + r.useLongHandleErrorCode(r.Name) +} + +// Fn describes syscall function. +type Fn struct { + Name string + Params []*Param + Rets *Rets + PrintTrace bool + dllname string + dllfuncname string + src string + // TODO: get rid of this field and just use parameter index instead + curTmpVarIdx int // insure tmp variables have uniq names +} + +// extractParams parses s to extract function parameters. +func extractParams(s string, f *Fn) ([]*Param, error) { + s = trim(s) + if s == "" { + return nil, nil + } + a := strings.Split(s, ",") + ps := make([]*Param, len(a)) + for i := range ps { + s2 := trim(a[i]) + b := strings.Split(s2, " ") + if len(b) != 2 { + b = strings.Split(s2, "\t") + if len(b) != 2 { + return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"") + } + } + ps[i] = &Param{ + Name: trim(b[0]), + Type: trim(b[1]), + fn: f, + tmpVarIdx: -1, + } + } + return ps, nil +} + +// extractSection extracts text out of string s starting after start +// and ending just before end. found return value will indicate success, +// and prefix, body and suffix will contain correspondent parts of string s. +func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) { + s = trim(s) + if strings.HasPrefix(s, string(start)) { + // no prefix + body = s[1:] + } else { + a := strings.SplitN(s, string(start), 2) + if len(a) != 2 { + return "", "", s, false + } + prefix = a[0] + body = a[1] + } + a := strings.SplitN(body, string(end), 2) + if len(a) != 2 { + return "", "", "", false + } + return prefix, a[0], a[1], true +} + +// newFn parses string s and return created function Fn. +func newFn(s string) (*Fn, error) { + s = trim(s) + f := &Fn{ + Rets: &Rets{}, + src: s, + PrintTrace: *printTraceFlag, + } + // function name and args + prefix, body, s, found := extractSection(s, '(', ')') + if !found || prefix == "" { + return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"") + } + f.Name = prefix + var err error + f.Params, err = extractParams(body, f) + if err != nil { + return nil, err + } + // return values + _, body, s, found = extractSection(s, '(', ')') + if found { + r, err := extractParams(body, f) + if err != nil { + return nil, err + } + switch len(r) { + case 0: + case 1: + if r[0].IsError() { + f.Rets.ReturnsError = true + } else { + f.Rets.Name = r[0].Name + f.Rets.Type = r[0].Type + } + case 2: + if !r[1].IsError() { + return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"") + } + f.Rets.ReturnsError = true + f.Rets.Name = r[0].Name + f.Rets.Type = r[0].Type + default: + return nil, errors.New("Too many return values in \"" + f.src + "\"") + } + } + // fail condition + _, body, s, found = extractSection(s, '[', ']') + if found { + f.Rets.FailCond = body + } + // dll and dll function names + s = trim(s) + if s == "" { + return f, nil + } + if !strings.HasPrefix(s, "=") { + return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") + } + s = trim(s[1:]) + a := strings.Split(s, ".") + switch len(a) { + case 1: + f.dllfuncname = a[0] + case 2: + f.dllname = a[0] + f.dllfuncname = a[1] + default: + return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") + } + if n := f.dllfuncname; strings.HasSuffix(n, "?") { + f.dllfuncname = n[:len(n)-1] + f.Rets.fnMaybeAbsent = true + } + return f, nil +} + +// DLLName returns DLL name for function f. +func (f *Fn) DLLName() string { + if f.dllname == "" { + return "kernel32" + } + return f.dllname +} + +// DLLName returns DLL function name for function f. +func (f *Fn) DLLFuncName() string { + if f.dllfuncname == "" { + return f.Name + } + return f.dllfuncname +} + +// ParamList returns source code for function f parameters. +func (f *Fn) ParamList() string { + return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ") +} + +// HelperParamList returns source code for helper function f parameters. +func (f *Fn) HelperParamList() string { + return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ") +} + +// ParamPrintList returns source code of trace printing part correspondent +// to syscall input parameters. +func (f *Fn) ParamPrintList() string { + return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) +} + +// ParamCount return number of syscall parameters for function f. +func (f *Fn) ParamCount() int { + n := 0 + for _, p := range f.Params { + n += len(p.SyscallArgList()) + } + return n +} + +// SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/... +// to use. It returns parameter count for correspondent SyscallX function. +func (f *Fn) SyscallParamCount() int { + n := f.ParamCount() + switch { + case n <= 3: + return 3 + case n <= 6: + return 6 + case n <= 9: + return 9 + case n <= 12: + return 12 + case n <= 15: + return 15 + default: + panic("too many arguments to system call") + } +} + +// Syscall determines which SyscallX function to use for function f. +func (f *Fn) Syscall() string { + c := f.SyscallParamCount() + if c == 3 { + return syscalldot() + "Syscall" + } + return syscalldot() + "Syscall" + strconv.Itoa(c) +} + +// SyscallParamList returns source code for SyscallX parameters for function f. +func (f *Fn) SyscallParamList() string { + a := make([]string, 0) + for _, p := range f.Params { + a = append(a, p.SyscallArgList()...) + } + for len(a) < f.SyscallParamCount() { + a = append(a, "0") + } + return strings.Join(a, ", ") +} + +// HelperCallParamList returns source code of call into function f helper. +func (f *Fn) HelperCallParamList() string { + a := make([]string, 0, len(f.Params)) + for _, p := range f.Params { + s := p.Name + if p.Type == "string" { + s = p.tmpVar() + } + a = append(a, s) + } + return strings.Join(a, ", ") +} + +// MaybeAbsent returns source code for handling functions that are possibly unavailable. +func (p *Fn) MaybeAbsent() string { + if !p.Rets.fnMaybeAbsent { + return "" + } + const code = `%[1]s = proc%[2]s.Find() + if %[1]s != nil { + return + }` + errorVar := p.Rets.ErrorVarName() + if errorVar == "" { + errorVar = "err" + } + return fmt.Sprintf(code, errorVar, p.DLLFuncName()) +} + +// IsUTF16 is true, if f is W (utf16) function. It is false +// for all A (ascii) functions. +func (_ *Fn) IsUTF16() bool { + return true +} + +// StrconvFunc returns name of Go string to OS string function for f. +func (f *Fn) StrconvFunc() string { + if f.IsUTF16() { + return syscalldot() + "UTF16PtrFromString" + } + return syscalldot() + "BytePtrFromString" +} + +// StrconvType returns Go type name used for OS string for f. +func (f *Fn) StrconvType() string { + if f.IsUTF16() { + return "*uint16" + } + return "*byte" +} + +// HasStringParam is true, if f has at least one string parameter. +// Otherwise it is false. +func (f *Fn) HasStringParam() bool { + for _, p := range f.Params { + if p.Type == "string" { + return true + } + } + return false +} + +// HelperName returns name of function f helper. +func (f *Fn) HelperName() string { + if !f.HasStringParam() { + return f.Name + } + return "_" + f.Name +} + +// Source files and functions. +type Source struct { + Funcs []*Fn + Files []string + StdLibImports []string + ExternalImports []string +} + +func (src *Source) Import(pkg string) { + src.StdLibImports = append(src.StdLibImports, pkg) + sort.Strings(src.StdLibImports) +} + +func (src *Source) ExternalImport(pkg string) { + src.ExternalImports = append(src.ExternalImports, pkg) + sort.Strings(src.ExternalImports) +} + +// ParseFiles parses files listed in fs and extracts all syscall +// functions listed in sys comments. It returns source files +// and functions collection *Source if successful. +func ParseFiles(fs []string) (*Source, error) { + src := &Source{ + Funcs: make([]*Fn, 0), + Files: make([]string, 0), + StdLibImports: []string{ + "unsafe", + }, + ExternalImports: make([]string, 0), + } + for _, file := range fs { + if err := src.ParseFile(file); err != nil { + return nil, err + } + } + return src, nil +} + +// DLLs return dll names for a source set src. +func (src *Source) DLLs() []string { + uniq := make(map[string]bool) + r := make([]string, 0) + for _, f := range src.Funcs { + name := f.DLLName() + if _, found := uniq[name]; !found { + uniq[name] = true + r = append(r, name) + } + } + sort.Strings(r) + return r +} + +// ParseFile adds additional file path to a source set src. +func (src *Source) ParseFile(path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + s := bufio.NewScanner(file) + for s.Scan() { + t := trim(s.Text()) + if len(t) < 7 { + continue + } + if !strings.HasPrefix(t, "//sys") { + continue + } + t = t[5:] + if !(t[0] == ' ' || t[0] == '\t') { + continue + } + f, err := newFn(t[1:]) + if err != nil { + return err + } + src.Funcs = append(src.Funcs, f) + } + if err := s.Err(); err != nil { + return err + } + src.Files = append(src.Files, path) + sort.Slice(src.Funcs, func(i, j int) bool { + fi, fj := src.Funcs[i], src.Funcs[j] + if fi.DLLName() == fj.DLLName() { + return fi.DLLFuncName() < fj.DLLFuncName() + } + return fi.DLLName() < fj.DLLName() + }) + + // get package name + fset := token.NewFileSet() + _, err = file.Seek(0, 0) + if err != nil { + return err + } + pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly) + if err != nil { + return err + } + packageName = pkg.Name.Name + + return nil +} + +// IsStdRepo reports whether src is part of standard library. +func (src *Source) IsStdRepo() (bool, error) { + if len(src.Files) == 0 { + return false, errors.New("no input files provided") + } + abspath, err := filepath.Abs(src.Files[0]) + if err != nil { + return false, err + } + goroot := runtime.GOROOT() + if runtime.GOOS == "windows" { + abspath = strings.ToLower(abspath) + goroot = strings.ToLower(goroot) + } + sep := string(os.PathSeparator) + if !strings.HasSuffix(goroot, sep) { + goroot += sep + } + return strings.HasPrefix(abspath, goroot), nil +} + +// Generate output source file from a source set src. +func (src *Source) Generate(w io.Writer) error { + const ( + pkgStd = iota // any package in std library + pkgXSysWindows // x/sys/windows package + pkgOther + ) + isStdRepo, err := src.IsStdRepo() + if err != nil { + return err + } + var pkgtype int + switch { + case isStdRepo: + pkgtype = pkgStd + case packageName == "windows": + // TODO: this needs better logic than just using package name + pkgtype = pkgXSysWindows + default: + pkgtype = pkgOther + } + if *systemDLL { + switch pkgtype { + case pkgStd: + src.Import("internal/syscall/windows/sysdll") + case pkgXSysWindows: + default: + src.ExternalImport("golang.org/x/sys/windows") + } + } + if packageName != "syscall" { + src.Import("syscall") + } + funcMap := template.FuncMap{ + "packagename": packagename, + "syscalldot": syscalldot, + "newlazydll": func(dll string) string { + arg := "\"" + dll + ".dll\"" + if !*systemDLL { + return syscalldot() + "NewLazyDLL(" + arg + ")" + } + switch pkgtype { + case pkgStd: + return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))" + case pkgXSysWindows: + return "NewLazySystemDLL(" + arg + ")" + default: + return "windows.NewLazySystemDLL(" + arg + ")" + } + }, + } + t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate)) + err = t.Execute(w, src) + if err != nil { + return errors.New("Failed to execute template: " + err.Error()) + } + return nil +} + +func usage() { + fmt.Fprintf(os.Stderr, "usage: mksyscall_windows [flags] [path ...]\n") + flag.PrintDefaults() + os.Exit(1) +} + +func main() { + flag.Usage = usage + flag.Parse() + if len(flag.Args()) <= 0 { + fmt.Fprintf(os.Stderr, "no files to parse provided\n") + usage() + } + + src, err := ParseFiles(flag.Args()) + if err != nil { + log.Fatal(err) + } + + var buf bytes.Buffer + if err := src.Generate(&buf); err != nil { + log.Fatal(err) + } + + data, err := format.Source(buf.Bytes()) + if err != nil { + log.Fatal(err) + } + if *filename == "" { + _, err = os.Stdout.Write(data) + } else { + err = ioutil.WriteFile(*filename, data, 0644) + } + if err != nil { + log.Fatal(err) + } +} + +// TODO: use println instead to print in the following template +const srcTemplate = ` + +{{define "main"}}// Code generated by 'go generate'; DO NOT EDIT. + +package {{packagename}} + +import ( +{{range .StdLibImports}}"{{.}}" +{{end}} + +{{range .ExternalImports}}"{{.}}" +{{end}} +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = {{syscalldot}}EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e {{syscalldot}}Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( +{{template "dlls" .}} +{{template "funcnames" .}}) +{{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}} +{{end}} + +{{/* help functions */}} + +{{define "dlls"}}{{range .DLLs}} mod{{.}} = {{newlazydll .}} +{{end}}{{end}} + +{{define "funcnames"}}{{range .Funcs}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}") +{{end}}{{end}} + +{{define "helperbody"}} +func {{.Name}}({{.ParamList}}) {{template "results" .}}{ +{{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}}) +} +{{end}} + +{{define "funcbody"}} +func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{ +{{template "maybeabsent" .}} {{template "tmpvars" .}} {{template "syscall" .}} {{template "tmpvarsreadback" .}} +{{template "seterror" .}}{{template "printtrace" .}} return +} +{{end}} + +{{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}} +{{end}}{{end}}{{end}} + +{{define "maybeabsent"}}{{if .MaybeAbsent}}{{.MaybeAbsent}} +{{end}}{{end}} + +{{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}} +{{end}}{{end}}{{end}} + +{{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}} + +{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}} + +{{define "tmpvarsreadback"}}{{range .Params}}{{if .TmpVarReadbackCode}} +{{.TmpVarReadbackCode}}{{end}}{{end}}{{end}} + +{{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}} +{{end}}{{end}} + +{{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n") +{{end}}{{end}} + +` diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go b/internal/security/syscall_windows.go similarity index 61% rename from vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go rename to internal/security/syscall_windows.go index c40c2739b7..d7096716ce 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go +++ b/internal/security/syscall_windows.go @@ -2,6 +2,6 @@ package security //go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go -//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo -//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo -//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW +//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo +//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo +//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go b/internal/security/zsyscall_windows.go similarity index 59% rename from vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go rename to internal/security/zsyscall_windows.go index 4a90cb3cc8..4084680e0f 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go +++ b/internal/security/zsyscall_windows.go @@ -45,26 +45,26 @@ var ( procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo") ) -func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) - if r1 != 0 { - err = errnoErr(e1) +func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) { - r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } diff --git a/internal/tools/grantvmgroupaccess/main.go b/internal/tools/grantvmgroupaccess/main.go index 5fa6644d77..887db694d0 100644 --- a/internal/tools/grantvmgroupaccess/main.go +++ b/internal/tools/grantvmgroupaccess/main.go @@ -6,7 +6,7 @@ import ( "fmt" "os" - "github.com/Microsoft/go-winio/pkg/security" + "github.com/Microsoft/hcsshim/internal/security" ) func main() { diff --git a/internal/uvm/scsi.go b/internal/uvm/scsi.go index cc884c3bcd..e21d0ddfe2 100644 --- a/internal/uvm/scsi.go +++ b/internal/uvm/scsi.go @@ -12,7 +12,6 @@ import ( "path/filepath" "strings" - "github.com/Microsoft/go-winio/pkg/security" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -22,6 +21,7 @@ import ( "github.com/Microsoft/hcsshim/internal/log" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/security" "github.com/Microsoft/hcsshim/internal/wclayer" ) diff --git a/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go b/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go index a0e329edec..c3608dcec8 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go +++ b/test/vendor/github.com/Microsoft/hcsshim/computestorage/helpers.go @@ -8,11 +8,12 @@ import ( "path/filepath" "syscall" - "github.com/Microsoft/go-winio/pkg/security" "github.com/Microsoft/go-winio/vhd" "github.com/Microsoft/hcsshim/internal/memory" "github.com/pkg/errors" "golang.org/x/sys/windows" + + "github.com/Microsoft/hcsshim/internal/security" ) const defaultVHDXBlockSizeInMB = 1 diff --git a/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go b/test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go similarity index 90% rename from vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go rename to test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go index fca241590c..602920786c 100644 --- a/vendor/github.com/Microsoft/go-winio/pkg/security/grantvmgroupaccess.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go @@ -3,11 +3,10 @@ package security import ( + "fmt" "os" "syscall" "unsafe" - - "github.com/pkg/errors" ) type ( @@ -72,7 +71,7 @@ func GrantVmGroupAccess(name string) error { // Stat (to determine if `name` is a directory). s, err := os.Stat(name) if err != nil { - return errors.Wrapf(err, "%s os.Stat %s", gvmga, name) + return fmt.Errorf("%s os.Stat %s: %w", gvmga, name, err) } // Get a handle to the file/directory. Must defer Close on success. @@ -88,7 +87,7 @@ func GrantVmGroupAccess(name string) error { sd := uintptr(0) origDACL := uintptr(0) if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { - return errors.Wrapf(err, "%s GetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) @@ -102,7 +101,7 @@ func GrantVmGroupAccess(name string) error { // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { - return errors.Wrapf(err, "%s SetSecurityInfo %s", gvmga, name) + return fmt.Errorf("%s SetSecurityInfo %s: %w", gvmga, name, err) } return nil @@ -120,7 +119,7 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { } fd, err := syscall.CreateFile(&namep[0], da, sm, nil, syscall.OPEN_EXISTING, fa, 0) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.CreateFile %s", gvmga, name) + return 0, fmt.Errorf("%s syscall.CreateFile %s: %w", gvmga, name, err) } return fd, nil } @@ -131,7 +130,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp // Generate pointers to the SIDs based on the string SIDs sid, err := syscall.StringToSid(sidVmGroup) if err != nil { - return 0, errors.Wrapf(err, "%s syscall.StringToSid %s %s", gvmga, name, sidVmGroup) + return 0, fmt.Errorf("%s syscall.StringToSid %s %s: %w", gvmga, name, sidVmGroup, err) } inheritance := inheritModeNoInheritance @@ -154,7 +153,7 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp modifiedDACL := uintptr(0) if err := setEntriesInAcl(uintptr(uint32(1)), uintptr(unsafe.Pointer(&eaArray[0])), origDACL, &modifiedDACL); err != nil { - return 0, errors.Wrapf(err, "%s SetEntriesInAcl %s", gvmga, name) + return 0, fmt.Errorf("%s SetEntriesInAcl %s: %w", gvmga, name, err) } return modifiedDACL, nil diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go b/test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go similarity index 61% rename from test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go rename to test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go index c40c2739b7..d7096716ce 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/security/syscall_windows.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go @@ -2,6 +2,6 @@ package security //go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go -//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) [failretval!=0] = advapi32.GetSecurityInfo -//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) [failretval!=0] = advapi32.SetSecurityInfo -//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) [failretval!=0] = advapi32.SetEntriesInAclW +//sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo +//sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo +//sys setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) = advapi32.SetEntriesInAclW diff --git a/test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go b/test/vendor/github.com/Microsoft/hcsshim/internal/security/zsyscall_windows.go similarity index 59% rename from test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go rename to test/vendor/github.com/Microsoft/hcsshim/internal/security/zsyscall_windows.go index 4a90cb3cc8..4084680e0f 100644 --- a/test/vendor/github.com/Microsoft/go-winio/pkg/security/zsyscall_windows.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/security/zsyscall_windows.go @@ -45,26 +45,26 @@ var ( procSetSecurityInfo = modadvapi32.NewProc("SetSecurityInfo") ) -func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) - if r1 != 0 { - err = errnoErr(e1) +func getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procGetSecurityInfo.Addr(), 8, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(unsafe.Pointer(ppsidOwner)), uintptr(unsafe.Pointer(ppsidGroup)), uintptr(unsafe.Pointer(ppDacl)), uintptr(unsafe.Pointer(ppSacl)), uintptr(unsafe.Pointer(ppSecurityDescriptor)), 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (err error) { - r1, _, e1 := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setEntriesInAcl(count uintptr, pListOfEEs uintptr, oldAcl uintptr, newAcl *uintptr) (win32err error) { + r0, _, _ := syscall.Syscall6(procSetEntriesInAclW.Addr(), 4, uintptr(count), uintptr(pListOfEEs), uintptr(oldAcl), uintptr(unsafe.Pointer(newAcl)), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } -func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (err error) { - r1, _, e1 := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) - if r1 != 0 { - err = errnoErr(e1) +func setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) { + r0, _, _ := syscall.Syscall9(procSetSecurityInfo.Addr(), 7, uintptr(handle), uintptr(objectType), uintptr(si), uintptr(psidOwner), uintptr(psidGroup), uintptr(pDacl), uintptr(pSacl), 0, 0) + if r0 != 0 { + win32err = syscall.Errno(r0) } return } diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/scsi.go b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/scsi.go index cc884c3bcd..e21d0ddfe2 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/scsi.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/uvm/scsi.go @@ -12,7 +12,6 @@ import ( "path/filepath" "strings" - "github.com/Microsoft/go-winio/pkg/security" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -22,6 +21,7 @@ import ( "github.com/Microsoft/hcsshim/internal/log" "github.com/Microsoft/hcsshim/internal/protocol/guestrequest" "github.com/Microsoft/hcsshim/internal/protocol/guestresource" + "github.com/Microsoft/hcsshim/internal/security" "github.com/Microsoft/hcsshim/internal/wclayer" ) diff --git a/test/vendor/modules.txt b/test/vendor/modules.txt index b0d29873d5..e91c03c2c1 100644 --- a/test/vendor/modules.txt +++ b/test/vendor/modules.txt @@ -4,7 +4,6 @@ github.com/Microsoft/go-winio github.com/Microsoft/go-winio/backuptar github.com/Microsoft/go-winio/pkg/guid github.com/Microsoft/go-winio/pkg/process -github.com/Microsoft/go-winio/pkg/security github.com/Microsoft/go-winio/vhd # github.com/Microsoft/hcsshim v0.8.23 => ../ ## explicit; go 1.17 @@ -59,6 +58,7 @@ github.com/Microsoft/hcsshim/internal/resources github.com/Microsoft/hcsshim/internal/runhcs github.com/Microsoft/hcsshim/internal/safefile github.com/Microsoft/hcsshim/internal/schemaversion +github.com/Microsoft/hcsshim/internal/security github.com/Microsoft/hcsshim/internal/shimdiag github.com/Microsoft/hcsshim/internal/timeout github.com/Microsoft/hcsshim/internal/tools/securitypolicy/helpers diff --git a/vendor/golang.org/x/sys/execabs/execabs.go b/vendor/golang.org/x/sys/execabs/execabs.go new file mode 100644 index 0000000000..78192498db --- /dev/null +++ b/vendor/golang.org/x/sys/execabs/execabs.go @@ -0,0 +1,102 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package execabs is a drop-in replacement for os/exec +// that requires PATH lookups to find absolute paths. +// That is, execabs.Command("cmd") runs the same PATH lookup +// as exec.Command("cmd"), but if the result is a path +// which is relative, the Run and Start methods will report +// an error instead of running the executable. +// +// See https://blog.golang.org/path-security for more information +// about when it may be necessary or appropriate to use this package. +package execabs + +import ( + "context" + "fmt" + "os/exec" + "path/filepath" + "reflect" + "unsafe" +) + +// ErrNotFound is the error resulting if a path search failed to find an executable file. +// It is an alias for exec.ErrNotFound. +var ErrNotFound = exec.ErrNotFound + +// Cmd represents an external command being prepared or run. +// It is an alias for exec.Cmd. +type Cmd = exec.Cmd + +// Error is returned by LookPath when it fails to classify a file as an executable. +// It is an alias for exec.Error. +type Error = exec.Error + +// An ExitError reports an unsuccessful exit by a command. +// It is an alias for exec.ExitError. +type ExitError = exec.ExitError + +func relError(file, path string) error { + return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path) +} + +// LookPath searches for an executable named file in the directories +// named by the PATH environment variable. If file contains a slash, +// it is tried directly and the PATH is not consulted. The result will be +// an absolute path. +// +// LookPath differs from exec.LookPath in its handling of PATH lookups, +// which are used for file names without slashes. If exec.LookPath's +// PATH lookup would have returned an executable from the current directory, +// LookPath instead returns an error. +func LookPath(file string) (string, error) { + path, err := exec.LookPath(file) + if err != nil { + return "", err + } + if filepath.Base(file) == file && !filepath.IsAbs(path) { + return "", relError(file, path) + } + return path, nil +} + +func fixCmd(name string, cmd *exec.Cmd) { + if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) { + // exec.Command was called with a bare binary name and + // exec.LookPath returned a path which is not absolute. + // Set cmd.lookPathErr and clear cmd.Path so that it + // cannot be run. + lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer())) + if *lookPathErr == nil { + *lookPathErr = relError(name, cmd.Path) + } + cmd.Path = "" + } +} + +// CommandContext is like Command but includes a context. +// +// The provided context is used to kill the process (by calling os.Process.Kill) +// if the context becomes done before the command completes on its own. +func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { + cmd := exec.CommandContext(ctx, name, arg...) + fixCmd(name, cmd) + return cmd + +} + +// Command returns the Cmd struct to execute the named program with the given arguments. +// See exec.Command for most details. +// +// Command differs from exec.Command in its handling of PATH lookups, +// which are used when the program name contains no slashes. +// If exec.Command would have returned an exec.Cmd configured to run an +// executable from the current directory, Command instead +// returns an exec.Cmd that will return an error from Start or Run. +func Command(name string, arg ...string) *exec.Cmd { + cmd := exec.Command(name, arg...) + fixCmd(name, cmd) + return cmd +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6462b39e28..be16488c02 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -9,7 +9,6 @@ github.com/Microsoft/go-winio/pkg/etw github.com/Microsoft/go-winio/pkg/etwlogrus github.com/Microsoft/go-winio/pkg/guid github.com/Microsoft/go-winio/pkg/process -github.com/Microsoft/go-winio/pkg/security github.com/Microsoft/go-winio/vhd # github.com/cenkalti/backoff/v4 v4.1.1 ## explicit; go 1.13 @@ -208,6 +207,7 @@ golang.org/x/net/trace golang.org/x/sync/errgroup # golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e ## explicit; go 1.17 +golang.org/x/sys/execabs golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows From 6b920444509e2cd25ff44f5f918cf7fbc8ae438d Mon Sep 17 00:00:00 2001 From: Maksim An Date: Wed, 6 Apr 2022 18:59:51 -0700 Subject: [PATCH 15/16] Extend GrantVmGroupAccess to support write/execute/all permissions Add masks for GENERIC_WRITE, GENERIC_EXECUTE and GENERIC_ALL and update function signatures accordingly. Update grantvmgroupaccess tool to support granting permissions from above. Ignore various linter errors resurfaced after code copy-paste. Remove mksyscall_windows.go and update old unit tests and cleanup helper functions and packages used. Add unit test coverage for new functionality. Signed-off-by: Maksim An --- internal/security/grantvmgroupaccess.go | 82 +- internal/security/grantvmgroupaccess_test.go | 216 +++- internal/security/mksyscall_windows.go | 958 ------------------ internal/security/syscall_windows.go | 2 +- internal/tools/grantvmgroupaccess/main.go | 43 +- .../internal/security/grantvmgroupaccess.go | 82 +- .../internal/security/syscall_windows.go | 2 +- vendor/golang.org/x/sys/execabs/execabs.go | 102 -- vendor/modules.txt | 1 - 9 files changed, 348 insertions(+), 1140 deletions(-) delete mode 100644 internal/security/mksyscall_windows.go delete mode 100644 vendor/golang.org/x/sys/execabs/execabs.go diff --git a/internal/security/grantvmgroupaccess.go b/internal/security/grantvmgroupaccess.go index 602920786c..bfcc157699 100644 --- a/internal/security/grantvmgroupaccess.go +++ b/internal/security/grantvmgroupaccess.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package security @@ -19,25 +20,37 @@ type ( securityInformation uint32 trusteeForm uint32 trusteeType uint32 +) - explicitAccess struct { - accessPermissions accessMask - accessMode accessMode - inheritance inheritMode - trustee trustee - } +type explicitAccess struct { + //nolint:structcheck + accessPermissions accessMask + //nolint:structcheck + accessMode accessMode + //nolint:structcheck + inheritance inheritMode + //nolint:structcheck + trustee trustee +} - trustee struct { - multipleTrustee *trustee - multipleTrusteeOperation int32 - trusteeForm trusteeForm - trusteeType trusteeType - name uintptr - } -) +type trustee struct { + //nolint:unused,structcheck + multipleTrustee *trustee + //nolint:unused,structcheck + multipleTrusteeOperation int32 + trusteeForm trusteeForm + trusteeType trusteeType + name uintptr +} const ( - accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ + AccessMaskNone accessMask = 0 + AccessMaskRead accessMask = 1 << 31 // GENERIC_READ + AccessMaskWrite accessMask = 1 << 30 // GENERIC_WRITE + AccessMaskExecute accessMask = 1 << 29 // GENERIC_EXECUTE + AccessMaskAll accessMask = 1 << 28 // GENERIC_ALL + + accessMaskDesiredPermission = AccessMaskRead accessModeGrant accessMode = 1 @@ -56,6 +69,7 @@ const ( shareModeRead shareMode = 0x1 shareModeWrite shareMode = 0x2 + //nolint:stylecheck // ST1003 sidVmGroup = "S-1-5-83-0" trusteeFormIsSid trusteeForm = 0 @@ -63,11 +77,20 @@ const ( trusteeTypeWellKnownGroup trusteeType = 5 ) -// GrantVMGroupAccess sets the DACL for a specified file or directory to +// GrantVmGroupAccess sets the DACL for a specified file or directory to // include Grant ACE entries for the VM Group SID. This is a golang re- // implementation of the same function in vmcompute, just not exported in // RS5. Which kind of sucks. Sucks a lot :/ -func GrantVmGroupAccess(name string) error { +func GrantVmGroupAccess(name string) error { //nolint:stylecheck // ST1003 + return GrantVmGroupAccessWithMask(name, accessMaskDesiredPermission) +} + +// GrantVmGroupAccessWithMask sets the desired DACL for a specified file or +// directory. +func GrantVmGroupAccessWithMask(name string, access accessMask) error { //nolint:stylecheck // ST1003 + if access == 0 || access<<4 != 0 { + return fmt.Errorf("invalid access mask: 0x%08x", access) + } // Stat (to determine if `name` is a directory). s, err := os.Stat(name) if err != nil { @@ -79,7 +102,9 @@ func GrantVmGroupAccess(name string) error { if err != nil { return err // Already wrapped } - defer syscall.CloseHandle(fd) + defer func() { + _ = syscall.CloseHandle(fd) + }() // Get the current DACL and Security Descriptor. Must defer LocalFree on success. ot := objectTypeFileObject @@ -89,15 +114,19 @@ func GrantVmGroupAccess(name string) error { if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } - defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) + defer func() { + _, _ = syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) + }() // Generate a new DACL which is the current DACL with the required ACEs added. // Must defer LocalFree on success. - newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL) + newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), access, origDACL) if err != nil { return err // Already wrapped } - defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) + defer func() { + _, _ = syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) + }() // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { @@ -110,7 +139,10 @@ func GrantVmGroupAccess(name string) error { // createFile is a helper function to call [Nt]CreateFile to get a handle to // the file or directory. func createFile(name string, isDir bool) (syscall.Handle, error) { - namep := syscall.StringToUTF16(name) + namep, err := syscall.UTF16FromString(name) + if err != nil { + return 0, fmt.Errorf("syscall.UTF16FromString %s: %w", name, err) + } da := uint32(desiredAccessReadControl | desiredAccessWriteDac) sm := uint32(shareModeRead | shareModeWrite) fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL) @@ -126,7 +158,7 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { // generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added. // The caller is responsible for LocalFree of the returned DACL on success. -func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) { +func generateDACLWithAcesAdded(name string, isDir bool, desiredAccess accessMask, origDACL uintptr) (uintptr, error) { // Generate pointers to the SIDs based on the string SIDs sid, err := syscall.StringToSid(sidVmGroup) if err != nil { @@ -139,8 +171,8 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp } eaArray := []explicitAccess{ - explicitAccess{ - accessPermissions: accessMaskDesiredPermission, + { + accessPermissions: desiredAccess, accessMode: accessModeGrant, inheritance: inheritance, trustee: trustee{ diff --git a/internal/security/grantvmgroupaccess_test.go b/internal/security/grantvmgroupaccess_test.go index 2dffaa1284..469fdf076f 100644 --- a/internal/security/grantvmgroupaccess_test.go +++ b/internal/security/grantvmgroupaccess_test.go @@ -1,16 +1,15 @@ -//+build windows +//go:build windows +// +build windows package security import ( - "io/ioutil" + "fmt" "os" + "os/exec" "path/filepath" "regexp" - "strings" "testing" - - exec "golang.org/x/sys/execabs" ) const ( @@ -35,37 +34,38 @@ const ( // S-1-15-3-1024-2268835264-3721307629-241982045-173645152-1490879176-104643441-2915960892-1612460704:(I)(R,W) // S-1-5-83-1-3166535780-1122986932-343720105-43916321:(I)(R,W) -func TestGrantVmGroupAccess(t *testing.T) { - f, err := ioutil.TempFile("", "gvmgafile") +func TestGrantVmGroupAccessDefault(t *testing.T) { + f1Path := filepath.Join(t.TempDir(), "gvmgafile") + f, err := os.Create(f1Path) if err != nil { t.Fatal(err) } defer func() { - f.Close() - os.Remove(f.Name()) + _ = f.Close() + _ = os.Remove(f1Path) }() - d, err := ioutil.TempDir("", "gvmgadir") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(d) - - find, err := os.Create(filepath.Join(d, "find.txt")) + dir2 := t.TempDir() + f2Path := filepath.Join(dir2, "find.txt") + find, err := os.Create(f2Path) if err != nil { t.Fatal(err) } + defer func() { + _ = find.Close() + _ = os.Remove(f2Path) + }() - if err := GrantVmGroupAccess(f.Name()); err != nil { + if err := GrantVmGroupAccess(f1Path); err != nil { t.Fatal(err) } - if err := GrantVmGroupAccess(d); err != nil { + if err := GrantVmGroupAccess(dir2); err != nil { t.Fatal(err) } verifyVMAccountDACLs(t, - f.Name(), + f1Path, []string{`(R)`}, ) @@ -80,17 +80,186 @@ func TestGrantVmGroupAccess(t *testing.T) { // In properties for the directory, advanced security settings, this will // show as a single line "Allow/Virtual Machines/Read/Inherited from none/This folder, subfolder and files verifyVMAccountDACLs(t, - d, + dir2, []string{`(R)`, `(OI)(CI)(IO)(GR)`}, ) verifyVMAccountDACLs(t, - find.Name(), + f2Path, []string{`(I)(R)`}, ) } +func TestGrantVMGroupAccess_File_DesiredPermissions(t *testing.T) { + type config struct { + name string + desiredAccess accessMask + expectedPermissions []string + } + + for _, cfg := range []config{ + { + name: "Read", + desiredAccess: AccessMaskRead, + expectedPermissions: []string{`(R)`}, + }, + { + name: "Write", + desiredAccess: AccessMaskWrite, + expectedPermissions: []string{`(W,Rc)`}, + }, + { + name: "Execute", + desiredAccess: AccessMaskExecute, + expectedPermissions: []string{`(Rc,S,X,RA)`}, + }, + { + name: "ReadWrite", + desiredAccess: AccessMaskRead | AccessMaskWrite, + expectedPermissions: []string{`(R,W)`}, + }, + { + name: "ReadExecute", + desiredAccess: AccessMaskRead | AccessMaskExecute, + expectedPermissions: []string{`(RX)`}, + }, + { + name: "WriteExecute", + desiredAccess: AccessMaskWrite | AccessMaskExecute, + expectedPermissions: []string{`(W,Rc,X,RA)`}, + }, + { + name: "ReadWriteExecute", + desiredAccess: AccessMaskRead | AccessMaskWrite | AccessMaskExecute, + expectedPermissions: []string{`(RX,W)`}, + }, + { + name: "All", + desiredAccess: AccessMaskAll, + expectedPermissions: []string{`(F)`}, + }, + } { + t.Run(cfg.name, func(t *testing.T) { + dir := t.TempDir() + fd, err := os.Create(filepath.Join(dir, "test.txt")) + if err != nil { + t.Fatalf("failed to create temporary file: %s", err) + } + defer func() { + _ = fd.Close() + _ = os.Remove(fd.Name()) + }() + + if err := GrantVmGroupAccessWithMask(fd.Name(), cfg.desiredAccess); err != nil { + t.Fatal(err) + } + verifyVMAccountDACLs(t, fd.Name(), cfg.expectedPermissions) + }) + } +} + +func TestGrantVMGroupAccess_Directory_Permissions(t *testing.T) { + type config struct { + name string + access accessMask + filePermissions []string + dirPermissions []string + } + + for _, cfg := range []config{ + { + name: "Read", + access: AccessMaskRead, + filePermissions: []string{`(I)(R)`}, + dirPermissions: []string{`(R)`, `(OI)(CI)(IO)(GR)`}, + }, + { + name: "Write", + access: AccessMaskWrite, + filePermissions: []string{`(I)(W,Rc)`}, + dirPermissions: []string{`(W,Rc)`, `(OI)(CI)(IO)(GW)`}, + }, + { + name: "Execute", + access: AccessMaskExecute, + filePermissions: []string{`(I)(Rc,S,X,RA)`}, + dirPermissions: []string{`(Rc,S,X,RA)`, `(OI)(CI)(IO)(GE)`}, + }, + { + name: "ReadWrite", + access: AccessMaskRead | AccessMaskWrite, + filePermissions: []string{`(I)(R,W)`}, + dirPermissions: []string{`(R,W)`, `(OI)(CI)(IO)(GR,GW)`}, + }, + { + name: "ReadExecute", + access: AccessMaskRead | AccessMaskExecute, + filePermissions: []string{`(I)(RX)`}, + dirPermissions: []string{`(RX)`, `(OI)(CI)(IO)(GR,GE)`}, + }, + { + name: "WriteExecute", + access: AccessMaskWrite | AccessMaskExecute, + filePermissions: []string{`(I)(W,Rc,X,RA)`}, + dirPermissions: []string{`(W,Rc,X,RA)`, `(OI)(CI)(IO)(GW,GE)`}, + }, + { + name: "ReadWriteExecute", + access: AccessMaskRead | AccessMaskWrite | AccessMaskExecute, + filePermissions: []string{`(I)(RX,W)`}, + dirPermissions: []string{`(RX,W)`, `(OI)(CI)(IO)(GR,GW,GE)`}, + }, + { + name: "All", + access: AccessMaskAll, + filePermissions: []string{`(I)(F)`}, + dirPermissions: []string{`(F)`, `(OI)(CI)(IO)(F)`}, + }} { + t.Run(cfg.name, func(t *testing.T) { + dir := t.TempDir() + fd, err := os.Create(filepath.Join(dir, "test.txt")) + if err != nil { + t.Fatalf("failed to create temporary file: %s", err) + } + defer func() { + _ = fd.Close() + _ = os.Remove(fd.Name()) + }() + + if err := GrantVmGroupAccessWithMask(dir, cfg.access); err != nil { + t.Fatal(err) + } + verifyVMAccountDACLs(t, dir, cfg.dirPermissions) + verifyVMAccountDACLs(t, fd.Name(), cfg.filePermissions) + }) + } +} + +func TestGrantVmGroupAccess_Invalid_AccessMask(t *testing.T) { + for _, access := range []accessMask{ + 0, // no bits set + 1, // invalid bit set + 0x02000001, // invalid extra bit set + } { + t.Run(fmt.Sprintf("AccessMask_0x%x", access), func(t *testing.T) { + dir := t.TempDir() + fd, err := os.Create(filepath.Join(dir, "test.txt")) + if err != nil { + t.Fatalf("failed to create temporary file: %s", err) + } + defer func() { + _ = fd.Close() + _ = os.Remove(fd.Name()) + }() + + if err := GrantVmGroupAccessWithMask(fd.Name(), access); err == nil { + t.Fatalf("expected an error for mask: %x", access) + } + }) + } +} + func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { cmd := exec.Command("icacls", name) outb, err := cmd.CombinedOutput() @@ -101,8 +270,7 @@ func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { for _, p := range permissions { // Avoid '(' and ')' being part of match groups - p = strings.Replace(p, "(", "\\(", -1) - p = strings.Replace(p, ")", "\\)", -1) + p = regexp.QuoteMeta(p) nameToCheck := vmAccountName + ":" + p sidToCheck := vmAccountSID + ":" + p @@ -114,7 +282,7 @@ func verifyVMAccountDACLs(t *testing.T, name string, permissions []string) { matchesSID := rxSID.FindAllStringIndex(out, -1) if len(matchesName) != 1 && len(matchesSID) != 1 { - t.Fatalf("expected one match for %s or %s\n%s", nameToCheck, sidToCheck, out) + t.Fatalf("expected one match for %s or %s\n%s\n", nameToCheck, sidToCheck, out) } } } diff --git a/internal/security/mksyscall_windows.go b/internal/security/mksyscall_windows.go deleted file mode 100644 index d52b6137c7..0000000000 --- a/internal/security/mksyscall_windows.go +++ /dev/null @@ -1,958 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Hard-coding unicode mode for VHD library. - -// +build ignore - -/* -mksyscall_windows generates windows system call bodies - -It parses all files specified on command line containing function -prototypes (like syscall_windows.go) and prints system call bodies -to standard output. - -The prototypes are marked by lines beginning with "//sys" and read -like func declarations if //sys is replaced by func, but: - -* The parameter lists must give a name for each argument. This - includes return parameters. - -* The parameter lists must give a type for each argument: - the (x, y, z int) shorthand is not allowed. - -* If the return parameter is an error number, it must be named err. - -* If go func name needs to be different from its winapi dll name, - the winapi name could be specified at the end, after "=" sign, like - //sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA - -* Each function that returns err needs to supply a condition, that - return value of winapi will be tested against to detect failure. - This would set err to windows "last-error", otherwise it will be nil. - The value can be provided at end of //sys declaration, like - //sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA - and is [failretval==0] by default. - -* If the function name ends in a "?", then the function not existing is non- - fatal, and an error will be returned instead of panicking. - -Usage: - mksyscall_windows [flags] [path ...] - -The flags are: - -output - Specify output file name (outputs to console if blank). - -trace - Generate print statement after every syscall. -*/ -package main - -import ( - "bufio" - "bytes" - "errors" - "flag" - "fmt" - "go/format" - "go/parser" - "go/token" - "io" - "io/ioutil" - "log" - "os" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "text/template" -) - -var ( - filename = flag.String("output", "", "output file name (standard output if omitted)") - printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall") - systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory") -) - -func trim(s string) string { - return strings.Trim(s, " \t") -} - -var packageName string - -func packagename() string { - return packageName -} - -func syscalldot() string { - if packageName == "syscall" { - return "" - } - return "syscall." -} - -// Param is function parameter -type Param struct { - Name string - Type string - fn *Fn - tmpVarIdx int -} - -// tmpVar returns temp variable name that will be used to represent p during syscall. -func (p *Param) tmpVar() string { - if p.tmpVarIdx < 0 { - p.tmpVarIdx = p.fn.curTmpVarIdx - p.fn.curTmpVarIdx++ - } - return fmt.Sprintf("_p%d", p.tmpVarIdx) -} - -// BoolTmpVarCode returns source code for bool temp variable. -func (p *Param) BoolTmpVarCode() string { - const code = `var %[1]s uint32 - if %[2]s { - %[1]s = 1 - }` - return fmt.Sprintf(code, p.tmpVar(), p.Name) -} - -// BoolPointerTmpVarCode returns source code for bool temp variable. -func (p *Param) BoolPointerTmpVarCode() string { - const code = `var %[1]s uint32 - if *%[2]s { - %[1]s = 1 - }` - return fmt.Sprintf(code, p.tmpVar(), p.Name) -} - -// SliceTmpVarCode returns source code for slice temp variable. -func (p *Param) SliceTmpVarCode() string { - const code = `var %s *%s - if len(%s) > 0 { - %s = &%s[0] - }` - tmp := p.tmpVar() - return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name) -} - -// StringTmpVarCode returns source code for string temp variable. -func (p *Param) StringTmpVarCode() string { - errvar := p.fn.Rets.ErrorVarName() - if errvar == "" { - errvar = "_" - } - tmp := p.tmpVar() - const code = `var %s %s - %s, %s = %s(%s)` - s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name) - if errvar == "-" { - return s - } - const morecode = ` - if %s != nil { - return - }` - return s + fmt.Sprintf(morecode, errvar) -} - -// TmpVarCode returns source code for temp variable. -func (p *Param) TmpVarCode() string { - switch { - case p.Type == "bool": - return p.BoolTmpVarCode() - case p.Type == "*bool": - return p.BoolPointerTmpVarCode() - case strings.HasPrefix(p.Type, "[]"): - return p.SliceTmpVarCode() - default: - return "" - } -} - -// TmpVarReadbackCode returns source code for reading back the temp variable into the original variable. -func (p *Param) TmpVarReadbackCode() string { - switch { - case p.Type == "*bool": - return fmt.Sprintf("*%s = %s != 0", p.Name, p.tmpVar()) - default: - return "" - } -} - -// TmpVarHelperCode returns source code for helper's temp variable. -func (p *Param) TmpVarHelperCode() string { - if p.Type != "string" { - return "" - } - return p.StringTmpVarCode() -} - -// SyscallArgList returns source code fragments representing p parameter -// in syscall. Slices are translated into 2 syscall parameters: pointer to -// the first element and length. -func (p *Param) SyscallArgList() []string { - t := p.HelperType() - var s string - switch { - case t == "*bool": - s = fmt.Sprintf("unsafe.Pointer(&%s)", p.tmpVar()) - case t[0] == '*': - s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name) - case t == "bool": - s = p.tmpVar() - case strings.HasPrefix(t, "[]"): - return []string{ - fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()), - fmt.Sprintf("uintptr(len(%s))", p.Name), - } - default: - s = p.Name - } - return []string{fmt.Sprintf("uintptr(%s)", s)} -} - -// IsError determines if p parameter is used to return error. -func (p *Param) IsError() bool { - return p.Name == "err" && p.Type == "error" -} - -// HelperType returns type of parameter p used in helper function. -func (p *Param) HelperType() string { - if p.Type == "string" { - return p.fn.StrconvType() - } - return p.Type -} - -// join concatenates parameters ps into a string with sep separator. -// Each parameter is converted into string by applying fn to it -// before conversion. -func join(ps []*Param, fn func(*Param) string, sep string) string { - if len(ps) == 0 { - return "" - } - a := make([]string, 0) - for _, p := range ps { - a = append(a, fn(p)) - } - return strings.Join(a, sep) -} - -// Rets describes function return parameters. -type Rets struct { - Name string - Type string - ReturnsError bool - FailCond string - fnMaybeAbsent bool -} - -// ErrorVarName returns error variable name for r. -func (r *Rets) ErrorVarName() string { - if r.ReturnsError { - return "err" - } - if r.Type == "error" { - return r.Name - } - return "" -} - -// ToParams converts r into slice of *Param. -func (r *Rets) ToParams() []*Param { - ps := make([]*Param, 0) - if len(r.Name) > 0 { - ps = append(ps, &Param{Name: r.Name, Type: r.Type}) - } - if r.ReturnsError { - ps = append(ps, &Param{Name: "err", Type: "error"}) - } - return ps -} - -// List returns source code of syscall return parameters. -func (r *Rets) List() string { - s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ") - if len(s) > 0 { - s = "(" + s + ")" - } else if r.fnMaybeAbsent { - s = "(err error)" - } - return s -} - -// PrintList returns source code of trace printing part correspondent -// to syscall return values. -func (r *Rets) PrintList() string { - return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) -} - -// SetReturnValuesCode returns source code that accepts syscall return values. -func (r *Rets) SetReturnValuesCode() string { - if r.Name == "" && !r.ReturnsError { - return "" - } - retvar := "r0" - if r.Name == "" { - retvar = "r1" - } - errvar := "_" - if r.ReturnsError { - errvar = "e1" - } - return fmt.Sprintf("%s, _, %s := ", retvar, errvar) -} - -func (r *Rets) useLongHandleErrorCode(retvar string) string { - const code = `if %s { - err = errnoErr(e1) - }` - cond := retvar + " == 0" - if r.FailCond != "" { - cond = strings.Replace(r.FailCond, "failretval", retvar, 1) - } - return fmt.Sprintf(code, cond) -} - -// SetErrorCode returns source code that sets return parameters. -func (r *Rets) SetErrorCode() string { - const code = `if r0 != 0 { - %s = %sErrno(r0) - }` - if r.Name == "" && !r.ReturnsError { - return "" - } - if r.Name == "" { - return r.useLongHandleErrorCode("r1") - } - if r.Type == "error" { - return fmt.Sprintf(code, r.Name, syscalldot()) - } - s := "" - switch { - case r.Type[0] == '*': - s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type) - case r.Type == "bool": - s = fmt.Sprintf("%s = r0 != 0", r.Name) - default: - s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type) - } - if !r.ReturnsError { - return s - } - return s + "\n\t" + r.useLongHandleErrorCode(r.Name) -} - -// Fn describes syscall function. -type Fn struct { - Name string - Params []*Param - Rets *Rets - PrintTrace bool - dllname string - dllfuncname string - src string - // TODO: get rid of this field and just use parameter index instead - curTmpVarIdx int // insure tmp variables have uniq names -} - -// extractParams parses s to extract function parameters. -func extractParams(s string, f *Fn) ([]*Param, error) { - s = trim(s) - if s == "" { - return nil, nil - } - a := strings.Split(s, ",") - ps := make([]*Param, len(a)) - for i := range ps { - s2 := trim(a[i]) - b := strings.Split(s2, " ") - if len(b) != 2 { - b = strings.Split(s2, "\t") - if len(b) != 2 { - return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"") - } - } - ps[i] = &Param{ - Name: trim(b[0]), - Type: trim(b[1]), - fn: f, - tmpVarIdx: -1, - } - } - return ps, nil -} - -// extractSection extracts text out of string s starting after start -// and ending just before end. found return value will indicate success, -// and prefix, body and suffix will contain correspondent parts of string s. -func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) { - s = trim(s) - if strings.HasPrefix(s, string(start)) { - // no prefix - body = s[1:] - } else { - a := strings.SplitN(s, string(start), 2) - if len(a) != 2 { - return "", "", s, false - } - prefix = a[0] - body = a[1] - } - a := strings.SplitN(body, string(end), 2) - if len(a) != 2 { - return "", "", "", false - } - return prefix, a[0], a[1], true -} - -// newFn parses string s and return created function Fn. -func newFn(s string) (*Fn, error) { - s = trim(s) - f := &Fn{ - Rets: &Rets{}, - src: s, - PrintTrace: *printTraceFlag, - } - // function name and args - prefix, body, s, found := extractSection(s, '(', ')') - if !found || prefix == "" { - return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"") - } - f.Name = prefix - var err error - f.Params, err = extractParams(body, f) - if err != nil { - return nil, err - } - // return values - _, body, s, found = extractSection(s, '(', ')') - if found { - r, err := extractParams(body, f) - if err != nil { - return nil, err - } - switch len(r) { - case 0: - case 1: - if r[0].IsError() { - f.Rets.ReturnsError = true - } else { - f.Rets.Name = r[0].Name - f.Rets.Type = r[0].Type - } - case 2: - if !r[1].IsError() { - return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"") - } - f.Rets.ReturnsError = true - f.Rets.Name = r[0].Name - f.Rets.Type = r[0].Type - default: - return nil, errors.New("Too many return values in \"" + f.src + "\"") - } - } - // fail condition - _, body, s, found = extractSection(s, '[', ']') - if found { - f.Rets.FailCond = body - } - // dll and dll function names - s = trim(s) - if s == "" { - return f, nil - } - if !strings.HasPrefix(s, "=") { - return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") - } - s = trim(s[1:]) - a := strings.Split(s, ".") - switch len(a) { - case 1: - f.dllfuncname = a[0] - case 2: - f.dllname = a[0] - f.dllfuncname = a[1] - default: - return nil, errors.New("Could not extract dll name from \"" + f.src + "\"") - } - if n := f.dllfuncname; strings.HasSuffix(n, "?") { - f.dllfuncname = n[:len(n)-1] - f.Rets.fnMaybeAbsent = true - } - return f, nil -} - -// DLLName returns DLL name for function f. -func (f *Fn) DLLName() string { - if f.dllname == "" { - return "kernel32" - } - return f.dllname -} - -// DLLName returns DLL function name for function f. -func (f *Fn) DLLFuncName() string { - if f.dllfuncname == "" { - return f.Name - } - return f.dllfuncname -} - -// ParamList returns source code for function f parameters. -func (f *Fn) ParamList() string { - return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ") -} - -// HelperParamList returns source code for helper function f parameters. -func (f *Fn) HelperParamList() string { - return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ") -} - -// ParamPrintList returns source code of trace printing part correspondent -// to syscall input parameters. -func (f *Fn) ParamPrintList() string { - return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `) -} - -// ParamCount return number of syscall parameters for function f. -func (f *Fn) ParamCount() int { - n := 0 - for _, p := range f.Params { - n += len(p.SyscallArgList()) - } - return n -} - -// SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/... -// to use. It returns parameter count for correspondent SyscallX function. -func (f *Fn) SyscallParamCount() int { - n := f.ParamCount() - switch { - case n <= 3: - return 3 - case n <= 6: - return 6 - case n <= 9: - return 9 - case n <= 12: - return 12 - case n <= 15: - return 15 - default: - panic("too many arguments to system call") - } -} - -// Syscall determines which SyscallX function to use for function f. -func (f *Fn) Syscall() string { - c := f.SyscallParamCount() - if c == 3 { - return syscalldot() + "Syscall" - } - return syscalldot() + "Syscall" + strconv.Itoa(c) -} - -// SyscallParamList returns source code for SyscallX parameters for function f. -func (f *Fn) SyscallParamList() string { - a := make([]string, 0) - for _, p := range f.Params { - a = append(a, p.SyscallArgList()...) - } - for len(a) < f.SyscallParamCount() { - a = append(a, "0") - } - return strings.Join(a, ", ") -} - -// HelperCallParamList returns source code of call into function f helper. -func (f *Fn) HelperCallParamList() string { - a := make([]string, 0, len(f.Params)) - for _, p := range f.Params { - s := p.Name - if p.Type == "string" { - s = p.tmpVar() - } - a = append(a, s) - } - return strings.Join(a, ", ") -} - -// MaybeAbsent returns source code for handling functions that are possibly unavailable. -func (p *Fn) MaybeAbsent() string { - if !p.Rets.fnMaybeAbsent { - return "" - } - const code = `%[1]s = proc%[2]s.Find() - if %[1]s != nil { - return - }` - errorVar := p.Rets.ErrorVarName() - if errorVar == "" { - errorVar = "err" - } - return fmt.Sprintf(code, errorVar, p.DLLFuncName()) -} - -// IsUTF16 is true, if f is W (utf16) function. It is false -// for all A (ascii) functions. -func (_ *Fn) IsUTF16() bool { - return true -} - -// StrconvFunc returns name of Go string to OS string function for f. -func (f *Fn) StrconvFunc() string { - if f.IsUTF16() { - return syscalldot() + "UTF16PtrFromString" - } - return syscalldot() + "BytePtrFromString" -} - -// StrconvType returns Go type name used for OS string for f. -func (f *Fn) StrconvType() string { - if f.IsUTF16() { - return "*uint16" - } - return "*byte" -} - -// HasStringParam is true, if f has at least one string parameter. -// Otherwise it is false. -func (f *Fn) HasStringParam() bool { - for _, p := range f.Params { - if p.Type == "string" { - return true - } - } - return false -} - -// HelperName returns name of function f helper. -func (f *Fn) HelperName() string { - if !f.HasStringParam() { - return f.Name - } - return "_" + f.Name -} - -// Source files and functions. -type Source struct { - Funcs []*Fn - Files []string - StdLibImports []string - ExternalImports []string -} - -func (src *Source) Import(pkg string) { - src.StdLibImports = append(src.StdLibImports, pkg) - sort.Strings(src.StdLibImports) -} - -func (src *Source) ExternalImport(pkg string) { - src.ExternalImports = append(src.ExternalImports, pkg) - sort.Strings(src.ExternalImports) -} - -// ParseFiles parses files listed in fs and extracts all syscall -// functions listed in sys comments. It returns source files -// and functions collection *Source if successful. -func ParseFiles(fs []string) (*Source, error) { - src := &Source{ - Funcs: make([]*Fn, 0), - Files: make([]string, 0), - StdLibImports: []string{ - "unsafe", - }, - ExternalImports: make([]string, 0), - } - for _, file := range fs { - if err := src.ParseFile(file); err != nil { - return nil, err - } - } - return src, nil -} - -// DLLs return dll names for a source set src. -func (src *Source) DLLs() []string { - uniq := make(map[string]bool) - r := make([]string, 0) - for _, f := range src.Funcs { - name := f.DLLName() - if _, found := uniq[name]; !found { - uniq[name] = true - r = append(r, name) - } - } - sort.Strings(r) - return r -} - -// ParseFile adds additional file path to a source set src. -func (src *Source) ParseFile(path string) error { - file, err := os.Open(path) - if err != nil { - return err - } - defer file.Close() - - s := bufio.NewScanner(file) - for s.Scan() { - t := trim(s.Text()) - if len(t) < 7 { - continue - } - if !strings.HasPrefix(t, "//sys") { - continue - } - t = t[5:] - if !(t[0] == ' ' || t[0] == '\t') { - continue - } - f, err := newFn(t[1:]) - if err != nil { - return err - } - src.Funcs = append(src.Funcs, f) - } - if err := s.Err(); err != nil { - return err - } - src.Files = append(src.Files, path) - sort.Slice(src.Funcs, func(i, j int) bool { - fi, fj := src.Funcs[i], src.Funcs[j] - if fi.DLLName() == fj.DLLName() { - return fi.DLLFuncName() < fj.DLLFuncName() - } - return fi.DLLName() < fj.DLLName() - }) - - // get package name - fset := token.NewFileSet() - _, err = file.Seek(0, 0) - if err != nil { - return err - } - pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly) - if err != nil { - return err - } - packageName = pkg.Name.Name - - return nil -} - -// IsStdRepo reports whether src is part of standard library. -func (src *Source) IsStdRepo() (bool, error) { - if len(src.Files) == 0 { - return false, errors.New("no input files provided") - } - abspath, err := filepath.Abs(src.Files[0]) - if err != nil { - return false, err - } - goroot := runtime.GOROOT() - if runtime.GOOS == "windows" { - abspath = strings.ToLower(abspath) - goroot = strings.ToLower(goroot) - } - sep := string(os.PathSeparator) - if !strings.HasSuffix(goroot, sep) { - goroot += sep - } - return strings.HasPrefix(abspath, goroot), nil -} - -// Generate output source file from a source set src. -func (src *Source) Generate(w io.Writer) error { - const ( - pkgStd = iota // any package in std library - pkgXSysWindows // x/sys/windows package - pkgOther - ) - isStdRepo, err := src.IsStdRepo() - if err != nil { - return err - } - var pkgtype int - switch { - case isStdRepo: - pkgtype = pkgStd - case packageName == "windows": - // TODO: this needs better logic than just using package name - pkgtype = pkgXSysWindows - default: - pkgtype = pkgOther - } - if *systemDLL { - switch pkgtype { - case pkgStd: - src.Import("internal/syscall/windows/sysdll") - case pkgXSysWindows: - default: - src.ExternalImport("golang.org/x/sys/windows") - } - } - if packageName != "syscall" { - src.Import("syscall") - } - funcMap := template.FuncMap{ - "packagename": packagename, - "syscalldot": syscalldot, - "newlazydll": func(dll string) string { - arg := "\"" + dll + ".dll\"" - if !*systemDLL { - return syscalldot() + "NewLazyDLL(" + arg + ")" - } - switch pkgtype { - case pkgStd: - return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))" - case pkgXSysWindows: - return "NewLazySystemDLL(" + arg + ")" - default: - return "windows.NewLazySystemDLL(" + arg + ")" - } - }, - } - t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate)) - err = t.Execute(w, src) - if err != nil { - return errors.New("Failed to execute template: " + err.Error()) - } - return nil -} - -func usage() { - fmt.Fprintf(os.Stderr, "usage: mksyscall_windows [flags] [path ...]\n") - flag.PrintDefaults() - os.Exit(1) -} - -func main() { - flag.Usage = usage - flag.Parse() - if len(flag.Args()) <= 0 { - fmt.Fprintf(os.Stderr, "no files to parse provided\n") - usage() - } - - src, err := ParseFiles(flag.Args()) - if err != nil { - log.Fatal(err) - } - - var buf bytes.Buffer - if err := src.Generate(&buf); err != nil { - log.Fatal(err) - } - - data, err := format.Source(buf.Bytes()) - if err != nil { - log.Fatal(err) - } - if *filename == "" { - _, err = os.Stdout.Write(data) - } else { - err = ioutil.WriteFile(*filename, data, 0644) - } - if err != nil { - log.Fatal(err) - } -} - -// TODO: use println instead to print in the following template -const srcTemplate = ` - -{{define "main"}}// Code generated by 'go generate'; DO NOT EDIT. - -package {{packagename}} - -import ( -{{range .StdLibImports}}"{{.}}" -{{end}} - -{{range .ExternalImports}}"{{.}}" -{{end}} -) - -var _ unsafe.Pointer - -// Do the interface allocations only once for common -// Errno values. -const ( - errnoERROR_IO_PENDING = 997 -) - -var ( - errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING) - errERROR_EINVAL error = {{syscalldot}}EINVAL -) - -// errnoErr returns common boxed Errno values, to prevent -// allocations at runtime. -func errnoErr(e {{syscalldot}}Errno) error { - switch e { - case 0: - return errERROR_EINVAL - case errnoERROR_IO_PENDING: - return errERROR_IO_PENDING - } - // TODO: add more here, after collecting data on the common - // error values see on Windows. (perhaps when running - // all.bat?) - return e -} - -var ( -{{template "dlls" .}} -{{template "funcnames" .}}) -{{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}} -{{end}} - -{{/* help functions */}} - -{{define "dlls"}}{{range .DLLs}} mod{{.}} = {{newlazydll .}} -{{end}}{{end}} - -{{define "funcnames"}}{{range .Funcs}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}") -{{end}}{{end}} - -{{define "helperbody"}} -func {{.Name}}({{.ParamList}}) {{template "results" .}}{ -{{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}}) -} -{{end}} - -{{define "funcbody"}} -func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{ -{{template "maybeabsent" .}} {{template "tmpvars" .}} {{template "syscall" .}} {{template "tmpvarsreadback" .}} -{{template "seterror" .}}{{template "printtrace" .}} return -} -{{end}} - -{{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}} -{{end}}{{end}}{{end}} - -{{define "maybeabsent"}}{{if .MaybeAbsent}}{{.MaybeAbsent}} -{{end}}{{end}} - -{{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}} -{{end}}{{end}}{{end}} - -{{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}} - -{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}} - -{{define "tmpvarsreadback"}}{{range .Params}}{{if .TmpVarReadbackCode}} -{{.TmpVarReadbackCode}}{{end}}{{end}}{{end}} - -{{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}} -{{end}}{{end}} - -{{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n") -{{end}}{{end}} - -` diff --git a/internal/security/syscall_windows.go b/internal/security/syscall_windows.go index d7096716ce..f0cdd7d20c 100644 --- a/internal/security/syscall_windows.go +++ b/internal/security/syscall_windows.go @@ -1,6 +1,6 @@ package security -//go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go +//go:generate go run $GOPATH/src/golang.org/x/sys/windows/mkwinsyscall/mkwinsyscall.go -output zsyscall_windows.go syscall_windows.go //sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo //sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo diff --git a/internal/tools/grantvmgroupaccess/main.go b/internal/tools/grantvmgroupaccess/main.go index 887db694d0..c18a8f4134 100644 --- a/internal/tools/grantvmgroupaccess/main.go +++ b/internal/tools/grantvmgroupaccess/main.go @@ -3,6 +3,7 @@ package main import ( + "flag" "fmt" "os" @@ -10,11 +11,47 @@ import ( ) func main() { - if len(os.Args) != 2 { - fmt.Fprintln(os.Stderr, "Usage: grantvmgroupaccess.exe file") + readFlag := flag.Bool("read", false, "Grant GENERIC_READ permission (DEFAULT)") + writeFlag := flag.Bool("write", false, "Grant GENERIC_WRITE permission") + executeFlag := flag.Bool("execute", false, "Grant GENERIC_EXECUTE permission") + allFlag := flag.Bool("all", false, "Grant GENERIC_ALL permission") + + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "\nUsage of %s:\n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s [flags] file\n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Flags:\n") + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "Examples:\n") + fmt.Fprintf(os.Stderr, " %s --read myfile.txt\n", os.Args[0]) + fmt.Fprintf(os.Stderr, " %s --read --execute myfile.txt\n", os.Args[0]) + } + + flag.Parse() + + if flag.NArg() == 0 { + flag.Usage() os.Exit(-1) } - if err := security.GrantVmGroupAccess(os.Args[1]); err != nil { + + desiredAccess := security.AccessMaskNone + if flag.NFlag() == 0 { + desiredAccess = security.AccessMaskRead + } + + if *readFlag { + desiredAccess |= security.AccessMaskRead + } + if *writeFlag { + desiredAccess |= security.AccessMaskWrite + } + if *executeFlag { + desiredAccess |= security.AccessMaskExecute + } + if *allFlag { + desiredAccess |= security.AccessMaskAll + } + + if err := security.GrantVmGroupAccessWithMask(flag.Arg(0), desiredAccess); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(-1) } diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go b/test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go index 602920786c..bfcc157699 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/security/grantvmgroupaccess.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package security @@ -19,25 +20,37 @@ type ( securityInformation uint32 trusteeForm uint32 trusteeType uint32 +) - explicitAccess struct { - accessPermissions accessMask - accessMode accessMode - inheritance inheritMode - trustee trustee - } +type explicitAccess struct { + //nolint:structcheck + accessPermissions accessMask + //nolint:structcheck + accessMode accessMode + //nolint:structcheck + inheritance inheritMode + //nolint:structcheck + trustee trustee +} - trustee struct { - multipleTrustee *trustee - multipleTrusteeOperation int32 - trusteeForm trusteeForm - trusteeType trusteeType - name uintptr - } -) +type trustee struct { + //nolint:unused,structcheck + multipleTrustee *trustee + //nolint:unused,structcheck + multipleTrusteeOperation int32 + trusteeForm trusteeForm + trusteeType trusteeType + name uintptr +} const ( - accessMaskDesiredPermission accessMask = 1 << 31 // GENERIC_READ + AccessMaskNone accessMask = 0 + AccessMaskRead accessMask = 1 << 31 // GENERIC_READ + AccessMaskWrite accessMask = 1 << 30 // GENERIC_WRITE + AccessMaskExecute accessMask = 1 << 29 // GENERIC_EXECUTE + AccessMaskAll accessMask = 1 << 28 // GENERIC_ALL + + accessMaskDesiredPermission = AccessMaskRead accessModeGrant accessMode = 1 @@ -56,6 +69,7 @@ const ( shareModeRead shareMode = 0x1 shareModeWrite shareMode = 0x2 + //nolint:stylecheck // ST1003 sidVmGroup = "S-1-5-83-0" trusteeFormIsSid trusteeForm = 0 @@ -63,11 +77,20 @@ const ( trusteeTypeWellKnownGroup trusteeType = 5 ) -// GrantVMGroupAccess sets the DACL for a specified file or directory to +// GrantVmGroupAccess sets the DACL for a specified file or directory to // include Grant ACE entries for the VM Group SID. This is a golang re- // implementation of the same function in vmcompute, just not exported in // RS5. Which kind of sucks. Sucks a lot :/ -func GrantVmGroupAccess(name string) error { +func GrantVmGroupAccess(name string) error { //nolint:stylecheck // ST1003 + return GrantVmGroupAccessWithMask(name, accessMaskDesiredPermission) +} + +// GrantVmGroupAccessWithMask sets the desired DACL for a specified file or +// directory. +func GrantVmGroupAccessWithMask(name string, access accessMask) error { //nolint:stylecheck // ST1003 + if access == 0 || access<<4 != 0 { + return fmt.Errorf("invalid access mask: 0x%08x", access) + } // Stat (to determine if `name` is a directory). s, err := os.Stat(name) if err != nil { @@ -79,7 +102,9 @@ func GrantVmGroupAccess(name string) error { if err != nil { return err // Already wrapped } - defer syscall.CloseHandle(fd) + defer func() { + _ = syscall.CloseHandle(fd) + }() // Get the current DACL and Security Descriptor. Must defer LocalFree on success. ot := objectTypeFileObject @@ -89,15 +114,19 @@ func GrantVmGroupAccess(name string) error { if err := getSecurityInfo(fd, uint32(ot), uint32(si), nil, nil, &origDACL, nil, &sd); err != nil { return fmt.Errorf("%s GetSecurityInfo %s: %w", gvmga, name, err) } - defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) + defer func() { + _, _ = syscall.LocalFree((syscall.Handle)(unsafe.Pointer(sd))) + }() // Generate a new DACL which is the current DACL with the required ACEs added. // Must defer LocalFree on success. - newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), origDACL) + newDACL, err := generateDACLWithAcesAdded(name, s.IsDir(), access, origDACL) if err != nil { return err // Already wrapped } - defer syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) + defer func() { + _, _ = syscall.LocalFree((syscall.Handle)(unsafe.Pointer(newDACL))) + }() // And finally use SetSecurityInfo to apply the updated DACL. if err := setSecurityInfo(fd, uint32(ot), uint32(si), uintptr(0), uintptr(0), newDACL, uintptr(0)); err != nil { @@ -110,7 +139,10 @@ func GrantVmGroupAccess(name string) error { // createFile is a helper function to call [Nt]CreateFile to get a handle to // the file or directory. func createFile(name string, isDir bool) (syscall.Handle, error) { - namep := syscall.StringToUTF16(name) + namep, err := syscall.UTF16FromString(name) + if err != nil { + return 0, fmt.Errorf("syscall.UTF16FromString %s: %w", name, err) + } da := uint32(desiredAccessReadControl | desiredAccessWriteDac) sm := uint32(shareModeRead | shareModeWrite) fa := uint32(syscall.FILE_ATTRIBUTE_NORMAL) @@ -126,7 +158,7 @@ func createFile(name string, isDir bool) (syscall.Handle, error) { // generateDACLWithAcesAdded generates a new DACL with the two needed ACEs added. // The caller is responsible for LocalFree of the returned DACL on success. -func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintptr, error) { +func generateDACLWithAcesAdded(name string, isDir bool, desiredAccess accessMask, origDACL uintptr) (uintptr, error) { // Generate pointers to the SIDs based on the string SIDs sid, err := syscall.StringToSid(sidVmGroup) if err != nil { @@ -139,8 +171,8 @@ func generateDACLWithAcesAdded(name string, isDir bool, origDACL uintptr) (uintp } eaArray := []explicitAccess{ - explicitAccess{ - accessPermissions: accessMaskDesiredPermission, + { + accessPermissions: desiredAccess, accessMode: accessModeGrant, inheritance: inheritance, trustee: trustee{ diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go b/test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go index d7096716ce..f0cdd7d20c 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/security/syscall_windows.go @@ -1,6 +1,6 @@ package security -//go:generate go run mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go +//go:generate go run $GOPATH/src/golang.org/x/sys/windows/mkwinsyscall/mkwinsyscall.go -output zsyscall_windows.go syscall_windows.go //sys getSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, ppsidOwner **uintptr, ppsidGroup **uintptr, ppDacl *uintptr, ppSacl *uintptr, ppSecurityDescriptor *uintptr) (win32err error) = advapi32.GetSecurityInfo //sys setSecurityInfo(handle syscall.Handle, objectType uint32, si uint32, psidOwner uintptr, psidGroup uintptr, pDacl uintptr, pSacl uintptr) (win32err error) = advapi32.SetSecurityInfo diff --git a/vendor/golang.org/x/sys/execabs/execabs.go b/vendor/golang.org/x/sys/execabs/execabs.go deleted file mode 100644 index 78192498db..0000000000 --- a/vendor/golang.org/x/sys/execabs/execabs.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package execabs is a drop-in replacement for os/exec -// that requires PATH lookups to find absolute paths. -// That is, execabs.Command("cmd") runs the same PATH lookup -// as exec.Command("cmd"), but if the result is a path -// which is relative, the Run and Start methods will report -// an error instead of running the executable. -// -// See https://blog.golang.org/path-security for more information -// about when it may be necessary or appropriate to use this package. -package execabs - -import ( - "context" - "fmt" - "os/exec" - "path/filepath" - "reflect" - "unsafe" -) - -// ErrNotFound is the error resulting if a path search failed to find an executable file. -// It is an alias for exec.ErrNotFound. -var ErrNotFound = exec.ErrNotFound - -// Cmd represents an external command being prepared or run. -// It is an alias for exec.Cmd. -type Cmd = exec.Cmd - -// Error is returned by LookPath when it fails to classify a file as an executable. -// It is an alias for exec.Error. -type Error = exec.Error - -// An ExitError reports an unsuccessful exit by a command. -// It is an alias for exec.ExitError. -type ExitError = exec.ExitError - -func relError(file, path string) error { - return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path) -} - -// LookPath searches for an executable named file in the directories -// named by the PATH environment variable. If file contains a slash, -// it is tried directly and the PATH is not consulted. The result will be -// an absolute path. -// -// LookPath differs from exec.LookPath in its handling of PATH lookups, -// which are used for file names without slashes. If exec.LookPath's -// PATH lookup would have returned an executable from the current directory, -// LookPath instead returns an error. -func LookPath(file string) (string, error) { - path, err := exec.LookPath(file) - if err != nil { - return "", err - } - if filepath.Base(file) == file && !filepath.IsAbs(path) { - return "", relError(file, path) - } - return path, nil -} - -func fixCmd(name string, cmd *exec.Cmd) { - if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) { - // exec.Command was called with a bare binary name and - // exec.LookPath returned a path which is not absolute. - // Set cmd.lookPathErr and clear cmd.Path so that it - // cannot be run. - lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer())) - if *lookPathErr == nil { - *lookPathErr = relError(name, cmd.Path) - } - cmd.Path = "" - } -} - -// CommandContext is like Command but includes a context. -// -// The provided context is used to kill the process (by calling os.Process.Kill) -// if the context becomes done before the command completes on its own. -func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { - cmd := exec.CommandContext(ctx, name, arg...) - fixCmd(name, cmd) - return cmd - -} - -// Command returns the Cmd struct to execute the named program with the given arguments. -// See exec.Command for most details. -// -// Command differs from exec.Command in its handling of PATH lookups, -// which are used when the program name contains no slashes. -// If exec.Command would have returned an exec.Cmd configured to run an -// executable from the current directory, Command instead -// returns an exec.Cmd that will return an error from Start or Run. -func Command(name string, arg ...string) *exec.Cmd { - cmd := exec.Command(name, arg...) - fixCmd(name, cmd) - return cmd -} diff --git a/vendor/modules.txt b/vendor/modules.txt index be16488c02..a90aef84a6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -207,7 +207,6 @@ golang.org/x/net/trace golang.org/x/sync/errgroup # golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e ## explicit; go 1.17 -golang.org/x/sys/execabs golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows From dbb347e403747f0eb470bf9f17bf7ec32da14b0a Mon Sep 17 00:00:00 2001 From: Ameya Gawde Date: Mon, 25 Apr 2022 09:27:32 -0700 Subject: [PATCH 16/16] Adding ExternalPortReserved flag to NatPolicy for HNS API V1. THis is a flag exposed for docker to avoid port reservation conflict with external port (#1370) HNS API V2 will use NatFlags to check and see if ExternalPortReserved is set (cherry picked from commit b85f3fdc17dad534a2cebbc67e0c18f77fb0fca8) Signed-off-by: Ameya Gawde Co-authored-by: Kendall Stratton --- internal/hns/hnspolicy.go | 9 +++++---- .../Microsoft/hcsshim/internal/hns/hnspolicy.go | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/hns/hnspolicy.go b/internal/hns/hnspolicy.go index a80a0c9ba2..082c018a4e 100644 --- a/internal/hns/hnspolicy.go +++ b/internal/hns/hnspolicy.go @@ -21,10 +21,11 @@ const ( ) type NatPolicy struct { - Type PolicyType `json:"Type"` - Protocol string `json:",omitempty"` - InternalPort uint16 `json:",omitempty"` - ExternalPort uint16 `json:",omitempty"` + Type PolicyType `json:"Type"` + Protocol string `json:",omitempty"` + InternalPort uint16 `json:",omitempty"` + ExternalPort uint16 `json:",omitempty"` + ExternalPortReserved bool `json:",omitempty"` } type QosPolicy struct { diff --git a/test/vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go b/test/vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go index a80a0c9ba2..082c018a4e 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go +++ b/test/vendor/github.com/Microsoft/hcsshim/internal/hns/hnspolicy.go @@ -21,10 +21,11 @@ const ( ) type NatPolicy struct { - Type PolicyType `json:"Type"` - Protocol string `json:",omitempty"` - InternalPort uint16 `json:",omitempty"` - ExternalPort uint16 `json:",omitempty"` + Type PolicyType `json:"Type"` + Protocol string `json:",omitempty"` + InternalPort uint16 `json:",omitempty"` + ExternalPort uint16 `json:",omitempty"` + ExternalPortReserved bool `json:",omitempty"` } type QosPolicy struct {