...

Source file src/github.com/Microsoft/hcsshim/internal/uvm/start.go

Documentation: github.com/Microsoft/hcsshim/internal/uvm

     1  //go:build windows
     2  
     3  package uvm
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"crypto/rand"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"time"
    15  
    16  	"github.com/sirupsen/logrus"
    17  	"golang.org/x/sync/errgroup"
    18  	"golang.org/x/sys/windows"
    19  
    20  	"github.com/Microsoft/hcsshim/internal/gcs"
    21  	"github.com/Microsoft/hcsshim/internal/hcs"
    22  	"github.com/Microsoft/hcsshim/internal/hcs/schema1"
    23  	hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
    24  	"github.com/Microsoft/hcsshim/internal/log"
    25  	"github.com/Microsoft/hcsshim/internal/logfields"
    26  	"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
    27  	"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
    28  )
    29  
    30  // entropyBytes is the number of bytes of random data to send to a Linux UVM
    31  // during boot to seed the CRNG. There is not much point in making this too
    32  // large since the random data collected from the host is likely computed from a
    33  // relatively small key (256 bits?), so additional bytes would not actually
    34  // increase the entropy of the guest's pool. However, send enough to convince
    35  // containers that there is a large amount of entropy since this idea is
    36  // generally misunderstood.
    37  const entropyBytes = 512
    38  
    39  type gcsLogEntryStandard struct {
    40  	Time    time.Time    `json:"time"`
    41  	Level   logrus.Level `json:"level"`
    42  	Message string       `json:"msg"`
    43  }
    44  
    45  type gcsLogEntry struct {
    46  	gcsLogEntryStandard
    47  	Fields map[string]interface{}
    48  }
    49  
    50  // FUTURE-jstarks: Change the GCS log format to include type information
    51  // (e.g. by using a different encoding such as protobuf).
    52  func (e *gcsLogEntry) UnmarshalJSON(b []byte) error {
    53  	// Default the log level to info.
    54  	e.Level = logrus.InfoLevel
    55  	if err := json.Unmarshal(b, &e.gcsLogEntryStandard); err != nil {
    56  		return err
    57  	}
    58  	if err := json.Unmarshal(b, &e.Fields); err != nil {
    59  		return err
    60  	}
    61  	// Do not allow fatal or panic level errors to propagate.
    62  	if e.Level < logrus.ErrorLevel {
    63  		e.Level = logrus.ErrorLevel
    64  	}
    65  	// Clear special fields.
    66  	delete(e.Fields, "time")
    67  	delete(e.Fields, "level")
    68  	delete(e.Fields, "msg")
    69  	// Normalize floats to integers.
    70  	for k, v := range e.Fields {
    71  		if d, ok := v.(float64); ok && float64(int64(d)) == d {
    72  			e.Fields[k] = int64(d)
    73  		}
    74  	}
    75  	return nil
    76  }
    77  
    78  func isDisconnectError(err error) bool {
    79  	return hcs.IsAny(err, windows.WSAECONNABORTED, windows.WSAECONNRESET)
    80  }
    81  
    82  func parseLogrus(vmid string) func(r io.Reader) {
    83  	return func(r io.Reader) {
    84  		j := json.NewDecoder(r)
    85  		e := log.L.Dup()
    86  		fields := e.Data
    87  		for {
    88  			for k := range fields {
    89  				delete(fields, k)
    90  			}
    91  			gcsEntry := gcsLogEntry{Fields: e.Data}
    92  			err := j.Decode(&gcsEntry)
    93  			if err != nil {
    94  				// Something went wrong. Read the rest of the data as a single
    95  				// string and log it at once -- it's probably a GCS panic stack.
    96  				if !errors.Is(err, io.EOF) && !isDisconnectError(err) {
    97  					logrus.WithFields(logrus.Fields{
    98  						logfields.UVMID: vmid,
    99  						logrus.ErrorKey: err,
   100  					}).Error("gcs log read")
   101  				}
   102  				rest, _ := io.ReadAll(io.MultiReader(j.Buffered(), r))
   103  				rest = bytes.TrimSpace(rest)
   104  				if len(rest) != 0 {
   105  					logrus.WithFields(logrus.Fields{
   106  						logfields.UVMID: vmid,
   107  						"stderr":        string(rest),
   108  					}).Error("gcs terminated")
   109  				}
   110  				break
   111  			}
   112  			fields[logfields.UVMID] = vmid
   113  			fields["vm.time"] = gcsEntry.Time
   114  			e.Log(gcsEntry.Level, gcsEntry.Message)
   115  		}
   116  	}
   117  }
   118  
   119  // When using an external GCS connection it is necessary to send a ModifySettings request
   120  // for HvSockt so that the GCS can setup some registry keys that are required for running
   121  // containers inside the UVM. In non external GCS connection scenarios this is done by the
   122  // HCS immediately after the GCS connection is done. Since, we are using the external GCS
   123  // connection we should do that setup here after we connect with the GCS.
   124  // This only applies for WCOW
   125  func (uvm *UtilityVM) configureHvSocketForGCS(ctx context.Context) (err error) {
   126  	if uvm.OS() != "windows" {
   127  		return nil
   128  	}
   129  
   130  	hvsocketAddress := &hcsschema.HvSocketAddress{
   131  		LocalAddress:  uvm.runtimeID.String(),
   132  		ParentAddress: gcs.WindowsGcsHvHostID.String(),
   133  	}
   134  
   135  	conSetupReq := &hcsschema.ModifySettingRequest{
   136  		GuestRequest: guestrequest.ModificationRequest{
   137  			RequestType:  guestrequest.RequestTypeUpdate,
   138  			ResourceType: guestresource.ResourceTypeHvSocket,
   139  			Settings:     hvsocketAddress,
   140  		},
   141  	}
   142  
   143  	if err = uvm.modify(ctx, conSetupReq); err != nil {
   144  		return fmt.Errorf("failed to configure HVSOCK for external GCS: %s", err)
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  // Start synchronously starts the utility VM.
   151  func (uvm *UtilityVM) Start(ctx context.Context) (err error) {
   152  	// save parent context, without timeout to use in terminate
   153  	pCtx := ctx
   154  	ctx, cancel := context.WithTimeout(pCtx, 2*time.Minute)
   155  	g, gctx := errgroup.WithContext(ctx)
   156  	defer func() {
   157  		_ = g.Wait()
   158  	}()
   159  	defer cancel()
   160  
   161  	// create exitCh ahead of time to prevent race conditions between writing
   162  	// initalizing the channel and waiting on it during acceptAndClose
   163  	uvm.exitCh = make(chan struct{})
   164  
   165  	// Prepare to provide entropy to the init process in the background. This
   166  	// must be done in a goroutine since, when using the internal bridge, the
   167  	// call to Start() will block until the GCS launches, and this cannot occur
   168  	// until the host accepts and closes the entropy connection.
   169  	if uvm.entropyListener != nil {
   170  		g.Go(func() error {
   171  			conn, err := uvm.acceptAndClose(gctx, uvm.entropyListener)
   172  			uvm.entropyListener = nil
   173  			if err != nil {
   174  				return fmt.Errorf("failed to connect to entropy socket: %s", err)
   175  			}
   176  			defer conn.Close()
   177  			_, err = io.CopyN(conn, rand.Reader, entropyBytes)
   178  			if err != nil {
   179  				return fmt.Errorf("failed to write entropy: %s", err)
   180  			}
   181  			return nil
   182  		})
   183  	}
   184  
   185  	if uvm.outputListener != nil {
   186  		g.Go(func() error {
   187  			conn, err := uvm.acceptAndClose(gctx, uvm.outputListener)
   188  			uvm.outputListener = nil
   189  			if err != nil {
   190  				close(uvm.outputProcessingDone)
   191  				return fmt.Errorf("failed to connect to log socket: %s", err)
   192  			}
   193  			go func() {
   194  				uvm.outputHandler(conn)
   195  				close(uvm.outputProcessingDone)
   196  			}()
   197  			return nil
   198  		})
   199  	}
   200  
   201  	err = uvm.hcsSystem.Start(ctx)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	defer func() {
   206  		if err != nil {
   207  			// use parent context, to prevent 2 minute timout (set above) from overridding terminate operation's
   208  			// timeout and erroring out prematurely
   209  			_ = uvm.hcsSystem.Terminate(pCtx)
   210  			_ = uvm.hcsSystem.Wait()
   211  		}
   212  	}()
   213  
   214  	// Start waiting on the utility VM.
   215  	go func() {
   216  		err := uvm.hcsSystem.Wait()
   217  		if err == nil {
   218  			err = uvm.hcsSystem.ExitError()
   219  		}
   220  		uvm.exitErr = err
   221  		close(uvm.exitCh)
   222  	}()
   223  
   224  	// Collect any errors from writing entropy or establishing the log
   225  	// connection.
   226  	if err = g.Wait(); err != nil {
   227  		return err
   228  	}
   229  
   230  	if uvm.gcListener != nil {
   231  		// Accept the GCS connection.
   232  		conn, err := uvm.acceptAndClose(ctx, uvm.gcListener)
   233  		uvm.gcListener = nil
   234  		if err != nil {
   235  			return fmt.Errorf("failed to connect to GCS: %s", err)
   236  		}
   237  
   238  		var initGuestState *gcs.InitialGuestState
   239  		if uvm.OS() == "windows" {
   240  			// Default to setting the time zone in the UVM to the hosts time zone unless the client asked to avoid this behavior. If so, assign
   241  			// to UTC.
   242  			if uvm.noInheritHostTimezone {
   243  				initGuestState = &gcs.InitialGuestState{
   244  					Timezone: utcTimezone,
   245  				}
   246  			} else {
   247  				tz, err := getTimezone()
   248  				if err != nil {
   249  					return err
   250  				}
   251  				initGuestState = &gcs.InitialGuestState{
   252  					Timezone: tz,
   253  				}
   254  			}
   255  		}
   256  		// Start the GCS protocol.
   257  		gcc := &gcs.GuestConnectionConfig{
   258  			Conn:           conn,
   259  			Log:            log.G(ctx).WithField(logfields.UVMID, uvm.id),
   260  			IoListen:       gcs.HvsockIoListen(uvm.runtimeID),
   261  			InitGuestState: initGuestState,
   262  		}
   263  		uvm.gc, err = gcc.Connect(ctx, true)
   264  		if err != nil {
   265  			return err
   266  		}
   267  		uvm.guestCaps = *uvm.gc.Capabilities()
   268  		uvm.protocol = uvm.gc.Protocol()
   269  
   270  		// initial setup required for external GCS connection
   271  		if err = uvm.configureHvSocketForGCS(ctx); err != nil {
   272  			return fmt.Errorf("failed to do initial GCS setup: %s", err)
   273  		}
   274  	} else {
   275  		// Cache the guest connection properties.
   276  		properties, err := uvm.hcsSystem.Properties(ctx, schema1.PropertyTypeGuestConnection)
   277  		if err != nil {
   278  			return err
   279  		}
   280  		uvm.guestCaps = properties.GuestConnectionInfo.GuestDefinedCapabilities
   281  		uvm.protocol = properties.GuestConnectionInfo.ProtocolVersion
   282  	}
   283  
   284  	if uvm.confidentialUVMOptions != nil && uvm.OS() == "linux" {
   285  		copts := []ConfidentialUVMOpt{
   286  			WithSecurityPolicy(uvm.confidentialUVMOptions.SecurityPolicy),
   287  			WithSecurityPolicyEnforcer(uvm.confidentialUVMOptions.SecurityPolicyEnforcer),
   288  			WithUVMReferenceInfo(defaultLCOWOSBootFilesPath(), uvm.confidentialUVMOptions.UVMReferenceInfoFile),
   289  		}
   290  		if err := uvm.SetConfidentialUVMOptions(ctx, copts...); err != nil {
   291  			return err
   292  		}
   293  	}
   294  
   295  	return nil
   296  }
   297  
   298  // acceptAndClose accepts a connection and then closes a listener. If the
   299  // context becomes done or the utility VM terminates, the operation will be
   300  // cancelled (but the listener will still be closed).
   301  func (uvm *UtilityVM) acceptAndClose(ctx context.Context, l net.Listener) (net.Conn, error) {
   302  	var conn net.Conn
   303  	ch := make(chan error)
   304  	go func() {
   305  		var err error
   306  		conn, err = l.Accept()
   307  		ch <- err
   308  	}()
   309  	select {
   310  	case err := <-ch:
   311  		l.Close()
   312  		return conn, err
   313  	case <-ctx.Done():
   314  	case <-uvm.exitCh:
   315  	}
   316  	l.Close()
   317  	err := <-ch
   318  	if err == nil {
   319  		return conn, err
   320  	}
   321  	// Prefer context error to VM error to accept error in order to return the
   322  	// most useful error.
   323  	if ctx.Err() != nil {
   324  		return nil, ctx.Err()
   325  	}
   326  	if uvm.exitErr != nil {
   327  		return nil, uvm.exitErr
   328  	}
   329  	return nil, err
   330  }
   331  

View as plain text