High severity8.8NVD Advisory· Published Dec 18, 2017· Updated May 13, 2026
CVE-2017-15103
CVE-2017-15103
Description
A security-check flaw was found in the way the Heketi 5 server API handled user requests. An authenticated Heketi user could send specially crafted requests to the Heketi server, resulting in remote command execution as the user running Heketi server and possibly privilege escalation.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/heketi/heketiGo | < 5.0.1 | 5.0.1 |
Affected products
1Patches
1787bae461b23api: input validation
7 files changed · +171 −4
apps/glusterfs/app_device.go+13 −0 modified@@ -28,6 +28,13 @@ func (a *App) DeviceAdd(w http.ResponseWriter, r *http.Request) { return } + err = msg.Validate() + if err != nil { + http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest) + logger.LogError("validation failed: " + err.Error()) + return + } + // Check the message has devices if msg.Name == "" { http.Error(w, "no devices added", http.StatusBadRequest) @@ -323,6 +330,12 @@ func (a *App) DeviceSetState(w http.ResponseWriter, r *http.Request) { http.Error(w, "request unable to be parsed", 422) return } + err = msg.Validate() + if err != nil { + http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest) + logger.LogError("validation failed: " + err.Error()) + return + } // Check for valid id, return immediately if not valid err = a.db.View(func(tx *bolt.Tx) error {
apps/glusterfs/app_node.go+13 −0 modified@@ -28,6 +28,13 @@ func (a *App) NodeAdd(w http.ResponseWriter, r *http.Request) { return } + err = msg.Validate() + if err != nil { + http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest) + logger.LogError("validation failed: " + err.Error()) + return + } + // Check information in JSON request if len(msg.Hostnames.Manage) == 0 { http.Error(w, "Manage hostname missing", http.StatusBadRequest) @@ -330,6 +337,12 @@ func (a *App) NodeSetState(w http.ResponseWriter, r *http.Request) { http.Error(w, "request unable to be parsed", 422) return } + err = msg.Validate() + if err != nil { + http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest) + logger.LogError("validation failed: " + err.Error()) + return + } // Check state is supported err = a.db.View(func(tx *bolt.Tx) error {
apps/glusterfs/app_volume.go+12 −0 modified@@ -34,6 +34,12 @@ func (a *App) VolumeCreate(w http.ResponseWriter, r *http.Request) { http.Error(w, "request unable to be parsed", 422) return } + err = msg.Validate() + if err != nil { + http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest) + logger.LogError("validation failed: " + err.Error()) + return + } switch { case msg.Gid < 0: @@ -287,6 +293,12 @@ func (a *App) VolumeExpand(w http.ResponseWriter, r *http.Request) { return } logger.Debug("Msg: %v", msg) + err = msg.Validate() + if err != nil { + http.Error(w, "validation failed: "+err.Error(), http.StatusBadRequest) + logger.LogError("validation failed: " + err.Error()) + return + } if msg.Size < 1 { http.Error(w, "Invalid volume size", http.StatusBadRequest)
executors/sshexec/device.go+2 −2 modified@@ -33,7 +33,7 @@ func (s *SshExecutor) DeviceSetup(host, device, vgid string) (d *executors.Devic // Setup commands commands := []string{ - fmt.Sprintf("pvcreate --metadatasize=128M --dataalignment=256K %v", device), + fmt.Sprintf("pvcreate --metadatasize=128M --dataalignment=256K '%v'", device), fmt.Sprintf("vgcreate %v %v", s.vgName(vgid), device), } @@ -65,7 +65,7 @@ func (s *SshExecutor) DeviceTeardown(host, device, vgid string) error { // Setup commands commands := []string{ fmt.Sprintf("vgremove %v", s.vgName(vgid)), - fmt.Sprintf("pvremove %v", device), + fmt.Sprintf("pvremove '%v'", device), } // Execute command
glide.lock+8 −2 modified@@ -1,6 +1,8 @@ -hash: 59834cf573ea7a48f90686852f6c02428d2ffee8ecfcfeafac4ca2e52638f00a -updated: 2017-05-22T14:00:49.364236742-04:00 +hash: 5fc263bfacc703e0a599166ccae968891dd53d7b4e42a3d8356b3194e45833bb +updated: 2017-12-18T17:52:34.69084539+05:30 imports: +- name: github.com/asaskevich/govalidator + version: 852d82c746b23d9b357b210ea470d99f4e023b72 - name: github.com/auth0/go-jwt-middleware version: f3f7de3b9e394e3af3b88e1b9457f6f71d1ae0ac - name: github.com/Azure/go-ansiterm @@ -53,6 +55,10 @@ imports: version: 6aced65f8501fe1217321abf0749d354824ba2ff - name: github.com/go-openapi/swag version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 +- name: github.com/go-ozzo/ozzo-validation + version: 85dcd8368eba387e65a03488b003e233994e87e9 + subpackages: + - is - name: github.com/gogo/protobuf version: e18d7aa8f8c624c915db340349aad4c49b10d173 subpackages:
glide.yaml+2 −0 modified@@ -22,3 +22,5 @@ import: - ssh/agent - package: k8s.io/client-go version: v3.0.0-beta.0 +- package: github.com/go-ozzo/ozzo-validation + version: v3.3
pkg/glusterfs/api/types.go+121 −0 modified@@ -18,9 +18,36 @@ package api import ( "fmt" + "regexp" "sort" + + "github.com/go-ozzo/ozzo-validation" + "github.com/go-ozzo/ozzo-validation/is" +) + +var ( + // Restricting the deviceName to much smaller subset of Unix Path + // as unix path takes almost everything except NULL + deviceNameRe = regexp.MustCompile("^/[a-zA-Z0-9_./-]+$") + + // Volume name constraints decided by looking at + // "cli_validate_volname" function in cli-cmd-parser.c of gluster code + volumeNameRe = regexp.MustCompile("^[a-zA-Z0-9_-]+$") + + blockVolNameRe = regexp.MustCompile("^[a-zA-Z0-9_-]+$") ) +// ValidateUUID is written this way because heketi UUID does not +// conform to neither UUID v4 nor v5. +func ValidateUUID(value interface{}) error { + s, _ := value.(string) + err := validation.Validate(s, validation.RuneLength(32, 32), is.Hexadecimal) + if err != nil { + return fmt.Errorf("not a valid UUID") + } + return nil +} + // State type EntryState string @@ -31,6 +58,15 @@ const ( EntryStateFailed EntryState = "failed" ) +func ValidateEntryState(value interface{}) error { + s, _ := value.(string) + err := validation.Validate(s, validation.Required, validation.In(EntryStateOnline, EntryStateOffline, EntryStateFailed)) + if err != nil { + return fmt.Errorf("state requested is not valid") + } + return nil +} + type DurabilityType string const ( @@ -39,11 +75,26 @@ const ( DurabilityEC DurabilityType = "disperse" ) +func ValidateDurabilityType(value interface{}) error { + s, _ := value.(string) + err := validation.Validate(s, validation.Required, validation.In(DurabilityReplicate, DurabilityDistributeOnly, DurabilityEC)) + if err != nil { + return fmt.Errorf("durability type requested is not valid") + } + return nil +} + // Common type StateRequest struct { State EntryState `json:"state"` } +func (statereq StateRequest) Validate() error { + return validation.ValidateStruct(&statereq, + validation.Field(&statereq.State, validation.Required, validation.By(ValidateEntryState)), + ) +} + // Storage values in KB type StorageSize struct { Total uint64 `json:"total"` @@ -56,6 +107,35 @@ type HostAddresses struct { Storage sort.StringSlice `json:"storage"` } +func ValidateManagementHostname(value interface{}) error { + s, _ := value.(sort.StringSlice) + for _, fqdn := range s { + err := validation.Validate(fqdn, validation.Required, is.Host) + if err != nil { + return fmt.Errorf("Manage hostname should be valid hostname") + } + } + return nil +} + +func ValidateStorageHostname(value interface{}) error { + s, _ := value.(sort.StringSlice) + for _, ip := range s { + err := validation.Validate(ip, validation.Required, is.Host) + if err != nil { + return fmt.Errorf("Storage hostname should be valid IP") + } + } + return nil +} + +func (hostadd HostAddresses) Validate() error { + return validation.ValidateStruct(&hostadd, + validation.Field(&hostadd.Manage, validation.Required, validation.By(ValidateManagementHostname)), + validation.Field(&hostadd.Storage, validation.Required, validation.By(ValidateStorageHostname)), + ) +} + // Brick type BrickInfo struct { Id string `json:"id"` @@ -73,11 +153,24 @@ type Device struct { Name string `json:"name"` } +func (dev Device) Validate() error { + return validation.ValidateStruct(&dev, + validation.Field(&dev.Name, validation.Required, validation.Match(deviceNameRe)), + ) +} + type DeviceAddRequest struct { Device NodeId string `json:"node"` } +func (devAddReq DeviceAddRequest) Validate() error { + return validation.ValidateStruct(&devAddReq, + validation.Field(&devAddReq.Device, validation.Required), + validation.Field(&devAddReq.NodeId, validation.Required, validation.By(ValidateUUID)), + ) +} + type DeviceInfo struct { Device Storage StorageSize `json:"storage"` @@ -97,6 +190,14 @@ type NodeAddRequest struct { ClusterId string `json:"cluster"` } +func (req NodeAddRequest) Validate() error { + return validation.ValidateStruct(&req, + validation.Field(&req.Zone, validation.Required, validation.Min(1)), + validation.Field(&req.Hostnames, validation.Required), + validation.Field(&req.ClusterId, validation.Required, validation.By(ValidateUUID)), + ) +} + type NodeInfo struct { NodeAddRequest Id string `json:"id"` @@ -160,6 +261,20 @@ type VolumeCreateRequest struct { } `json:"snapshot"` } +func (volCreateRequest VolumeCreateRequest) Validate() error { + return validation.ValidateStruct(&volCreateRequest, + validation.Field(&volCreateRequest.Size, validation.Required, validation.Min(1)), + validation.Field(&volCreateRequest.Clusters, validation.By(ValidateUUID)), + validation.Field(&volCreateRequest.Name, validation.Match(volumeNameRe)), + validation.Field(&volCreateRequest.Durability, validation.Skip), + validation.Field(&volCreateRequest.Gid, validation.Skip), + validation.Field(&volCreateRequest.GlusterVolumeOptions, validation.Skip), + // This is possibly a bug in validation lib, ignore next two lines for now + // validation.Field(&volCreateRequest.Snapshot.Enable, validation.In(true, false)), + // validation.Field(&volCreateRequest.Snapshot.Factor, validation.Min(1.0)), + ) +} + type VolumeInfo struct { VolumeCreateRequest Id string `json:"id"` @@ -186,6 +301,12 @@ type VolumeExpandRequest struct { Size int `json:"expand_size"` } +func (volExpandReq VolumeExpandRequest) Validate() error { + return validation.ValidateStruct(&volExpandReq, + validation.Field(&volExpandReq.Size, validation.Required, validation.Min(1)), + ) +} + // Constructors func NewVolumeInfoResponse() *VolumeInfoResponse {
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- bugzilla.redhat.com/show_bug.cginvdIssue TrackingPatchWEB
- access.redhat.com/errata/RHSA-2017:3481nvdThird Party AdvisoryWEB
- access.redhat.com/security/cve/CVE-2017-15103nvdThird Party AdvisoryWEB
- github.com/advisories/GHSA-6g56-v9qg-jp92ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2017-15103ghsaADVISORY
- github.com/heketi/heketi/commit/787bae461b23003a4daa4d1d639016a754cf6b00ghsaWEB
- github.com/heketi/heketi/releases/tag/v5.0.1ghsaWEB
News mentions
0No linked articles in our index yet.