diff --git a/components/gitpod-protocol/data/gitpod-schema.json b/components/gitpod-protocol/data/gitpod-schema.json index 911828bf0c5e3b..d3f2054bb8502e 100644 --- a/components/gitpod-protocol/data/gitpod-schema.json +++ b/components/gitpod-protocol/data/gitpod-schema.json @@ -282,6 +282,25 @@ "type": "boolean", "deprecationMessage": "The 'experimentalNetwork' property is deprecated.", "description": "Experimental network configuration in workspaces (deprecated). Enabled by default" + }, + "coreDump": { + "type": "object", + "description": "Configure the default action of certain signals is to cause a process to terminate and produce a core dump file, a file containing an image of the process's memory at the time of termination. Disabled by default.", + "deprecationMessage": "The 'coreDump' property is experimental.", + "additionalProperties": false, + "properties": { + "enabled": { + "type": "boolean" + }, + "softLimit": { + "type": "number", + "description": "upper limit on the size of the core dump file that will be produced if it receives a core dump signal" + }, + "hardLimit": { + "type": "number", + "description": "the hard limit acts as a ceiling for the soft limit. For more details please check https://man7.org/linux/man-pages/man2/getrlimit.2.html" + } + } } }, "additionalProperties": false, diff --git a/components/gitpod-protocol/go/gitpod-config-types.go b/components/gitpod-protocol/go/gitpod-config-types.go index 54cd39c0c1ffb7..4bf72162ebad1a 100644 --- a/components/gitpod-protocol/go/gitpod-config-types.go +++ b/components/gitpod-protocol/go/gitpod-config-types.go @@ -23,6 +23,17 @@ type AdditionalRepositoriesItems struct { Url string `yaml:"url"` } +// CoreDump Configure the default action of certain signals is to cause a process to terminate and produce a core dump file, a file containing an image of the process's memory at the time of termination. Disabled by default. +type CoreDump struct { + Enabled bool `yaml:"enabled,omitempty"` + + // the hard limit acts as a ceiling for the soft limit. For more details please check https://man7.org/linux/man-pages/man2/getrlimit.2.html + HardLimit float64 `yaml:"hardLimit,omitempty"` + + // upper limit on the size of the core dump file that will be produced if it receives a core dump signal + SoftLimit float64 `yaml:"softLimit,omitempty"` +} + // Env Environment variables to set. type Env struct { } @@ -43,6 +54,9 @@ type GitpodConfig struct { // Path to where the repository should be checked out relative to `/workspace`. Defaults to the simple repository name. CheckoutLocation string `yaml:"checkoutLocation,omitempty"` + // Configure the default action of certain signals is to cause a process to terminate and produce a core dump file, a file containing an image of the process's memory at the time of termination. Disabled by default. + CoreDump *CoreDump `yaml:"coreDump,omitempty"` + // Experimental network configuration in workspaces (deprecated). Enabled by default ExperimentalNetwork bool `yaml:"experimentalNetwork,omitempty"` @@ -268,6 +282,76 @@ func (strct *AdditionalRepositoriesItems) UnmarshalJSON(b []byte) error { return nil } +func (strct *CoreDump) MarshalJSON() ([]byte, error) { + buf := bytes.NewBuffer(make([]byte, 0)) + buf.WriteString("{") + comma := false + // Marshal the "enabled" field + if comma { + buf.WriteString(",") + } + buf.WriteString("\"enabled\": ") + if tmp, err := json.Marshal(strct.Enabled); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + comma = true + // Marshal the "hardLimit" field + if comma { + buf.WriteString(",") + } + buf.WriteString("\"hardLimit\": ") + if tmp, err := json.Marshal(strct.HardLimit); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + comma = true + // Marshal the "softLimit" field + if comma { + buf.WriteString(",") + } + buf.WriteString("\"softLimit\": ") + if tmp, err := json.Marshal(strct.SoftLimit); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + comma = true + + buf.WriteString("}") + rv := buf.Bytes() + return rv, nil +} + +func (strct *CoreDump) UnmarshalJSON(b []byte) error { + var jsonMap map[string]json.RawMessage + if err := json.Unmarshal(b, &jsonMap); err != nil { + return err + } + // parse all the defined properties + for k, v := range jsonMap { + switch k { + case "enabled": + if err := json.Unmarshal([]byte(v), &strct.Enabled); err != nil { + return err + } + case "hardLimit": + if err := json.Unmarshal([]byte(v), &strct.HardLimit); err != nil { + return err + } + case "softLimit": + if err := json.Unmarshal([]byte(v), &strct.SoftLimit); err != nil { + return err + } + default: + return fmt.Errorf("additional property not allowed: \"" + k + "\"") + } + } + return nil +} + func (strct *Github) MarshalJSON() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) buf.WriteString("{") @@ -334,6 +418,17 @@ func (strct *GitpodConfig) MarshalJSON() ([]byte, error) { buf.Write(tmp) } comma = true + // Marshal the "coreDump" field + if comma { + buf.WriteString(",") + } + buf.WriteString("\"coreDump\": ") + if tmp, err := json.Marshal(strct.CoreDump); err != nil { + return nil, err + } else { + buf.Write(tmp) + } + comma = true // Marshal the "experimentalNetwork" field if comma { buf.WriteString(",") @@ -466,6 +561,10 @@ func (strct *GitpodConfig) UnmarshalJSON(b []byte) error { if err := json.Unmarshal([]byte(v), &strct.CheckoutLocation); err != nil { return err } + case "coreDump": + if err := json.Unmarshal([]byte(v), &strct.CoreDump); err != nil { + return err + } case "experimentalNetwork": if err := json.Unmarshal([]byte(v), &strct.ExperimentalNetwork); err != nil { return err diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 087f03e68e7fea..4f0226fedc49f0 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -804,6 +804,12 @@ export interface RepositoryCloneInformation { checkoutLocation?: string; } +export interface CoreDumpConfig { + enabled?: boolean; + softLimit?: number; + hardLimit?: number; +} + export interface WorkspaceConfig { mainConfiguration?: string; additionalRepositories?: RepositoryCloneInformation[]; @@ -816,6 +822,7 @@ export interface WorkspaceConfig { github?: GithubAppConfig; vscode?: VSCodeConfig; jetbrains?: JetBrainsConfig; + coreDump?: CoreDumpConfig; /** deprecated. Enabled by default **/ experimentalNetwork?: boolean; diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index d5c9f35da84f50..01593b38cb4e11 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -1417,6 +1417,19 @@ export class WorkspaceStarter { dotfileEnv.setValue(user.additionalData?.dotfileRepo || ""); envvars.push(dotfileEnv); + if (workspace.config.coreDump?.enabled) { + // default core dump size is 262144 blocks (if blocksize is 4096) + const defaultLimit:number=1073741824; + + const rLimitCore = new EnvironmentVariable(); + rLimitCore.setName("GITPOD_RLIMIT_CORE"); + rLimitCore.setValue(JSON.stringify({ + softLimit: workspace.config.coreDump?.softLimit || defaultLimit, + hardLimit: workspace.config.coreDump?.hardLimit || defaultLimit, + })); + envvars.push(rLimitCore); + } + const createGitpodTokenPromise = (async () => { const scopes = this.createDefaultGitpodAPITokenScopes(workspace, instance); const token = crypto.randomBytes(30).toString("hex"); diff --git a/components/workspacekit/cmd/rings.go b/components/workspacekit/cmd/rings.go index 1fdc2e60af4c6d..ad39aa9f756282 100644 --- a/components/workspacekit/cmd/rings.go +++ b/components/workspacekit/cmd/rings.go @@ -829,6 +829,27 @@ var ring2Cmd = &cobra.Command{ return } + type fakeRlimit struct { + Cur uint64 `json:"softLimit"` + Max uint64 `json:"hardLimit"` + } + + rLimitValue := os.Getenv("GITPOD_RLIMIT_CORE") + var rLimitCore fakeRlimit + err = json.Unmarshal([]byte(rLimitValue), &rLimitCore) + if err != nil { + log.WithError(err).WithField("data", rLimitValue).Error("cannot deserialize GITPOD_RLIMIT_CORE") + } + + // we either set a limit or explicitly disable core dumps by setting 0 as values + err = unix.Setrlimit(unix.RLIMIT_CORE, &unix.Rlimit{ + Cur: rLimitCore.Cur, + Max: rLimitCore.Max, + }) + if err != nil { + log.WithError(err).WithField("rlimit", rLimitCore).Error("cannot configure core dumps") + } + // Now that we're in our new root filesystem, including proc and all, we can load // our seccomp filter, and tell our parent about it. scmpFd, err := seccomp.LoadFilter() diff --git a/components/ws-manager/pkg/manager/create.go b/components/ws-manager/pkg/manager/create.go index 0c1445833fdc18..7b63c517f5232b 100644 --- a/components/ws-manager/pkg/manager/create.go +++ b/components/ws-manager/pkg/manager/create.go @@ -802,7 +802,8 @@ func (m *Manager) createWorkspaceEnvironment(startContext *startWorkspaceContext "GITPOD_RESOLVED_EXTENSIONS", "GITPOD_EXTERNAL_EXTENSIONS", "GITPOD_WORKSPACE_CLASS_INFO", - "GITPOD_IDE_ALIAS": + "GITPOD_IDE_ALIAS", + "GITPOD_RLIMIT_CORE": // these variables are allowed - don't skip them default: if strings.HasPrefix(e.Name, "GITPOD_") {