Skip to content

Commit

Permalink
Updates to Portable SR functionality
Browse files Browse the repository at this point in the history
Modifies functionality to understand the use of legacy format VDIs:
- Adds an option to search for a legacy format VDI if a new one is not found
- Handles the case where multiple legacy VDIs are identified
- Passes the appropriate option to `xe-restore-metadata` when needed

This is XSA-459 / CVE-2024-31144

Also update the subprocess calls to be compatible with python3

Signed-off-by: Alex Brett <[email protected]>
  • Loading branch information
alexbrett authored and lindig committed Jul 24, 2024
1 parent a4b346c commit 57d7a63
Showing 1 changed file with 135 additions and 37 deletions.
172 changes: 135 additions & 37 deletions plugins-base/XSFeatureDRRestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,93 @@
from XSConsoleStandard import *
import subprocess

def _listBackups(sr_uuid, vdi_uuid, legacy=False):
# list the available backups
Layout.Inst().TransientBanner(Lang("Found VDI, retrieving available backups..."))
command = ["%s/xe-restore-metadata" % (Config.Inst().HelperPath(),), "-l", "-u", sr_uuid, "-x", vdi_uuid]
if legacy:
command.append("-o")
cmd = subprocess.Popen(command,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
universal_newlines = True)
output, errput = cmd.communicate()
status = cmd.returncode
if status != 0:
raise Exception("(%s,%s)" % (output,errput))
Layout.Inst().PushDialogue(DRRestoreSelection(output, vdi_uuid, sr_uuid, legacy=legacy))

class DRRestoreVDISelection(Dialogue):
def __init__(self, sr_uuid, vdi_uuids):
Dialogue.__init__(self)

choices = []

self.sr_uuid = sr_uuid
self.vdi_uuids = vdi_uuids
index = 0
for choice in self.vdi_uuids:
cdef = ChoiceDef(choice, lambda i=index: self.HandleVDIChoice(i))
index = index + 1
choices.append(cdef)

self.testMenu = Menu(self, None, "", choices)
self.ChangeState('LISTVDIS')

def BuildPane(self):
pane = self.NewPane(DialoguePane(self.parent))
pane.TitleSet(Lang('Restore Virtual Machine Metadata'))
pane.AddBox()

def UpdateFieldsLISTVDIS(self):
pane = self.Pane()
pane.ResetFields()

pane.TitleSet("Available Metadata VDIs")
pane.AddTitleField(Lang("Select Metadata VDI to Restore From"))
pane.AddWarningField(Lang("You should only restore metadata from a trustworthy VDI; loading untrustworthy metadata may put your system at risk"))
pane.AddMenuField(self.testMenu)
pane.AddKeyHelpField( { Lang("<Enter>") : Lang("OK"), Lang("<Esc>") : Lang("Cancel") } )

def UpdateFields(self):
self.Pane().ResetPosition()
getattr(self, 'UpdateFields'+self.state)() # Despatch method named 'UpdateFields'+self.state

def ChangeState(self, inState):
self.state = inState
self.BuildPane()
self.UpdateFields()

def HandleVDIChoice(self, inChoice):
_listBackups(self.sr_uuid, self.vdi_uuids[inChoice], legacy=True)

def HandleKeyLISTVDIS(self, inKey):
handled = self.testMenu.HandleKey(inKey)
if not handled and inKey == 'KEY_LEFT':
Layout.Inst().PopDialogue()
handled = True
return handled

def HandleKey(self, inKey):
handled = False
if hasattr(self, 'HandleKey'+self.state):
handled = getattr(self, 'HandleKey'+self.state)(inKey)

if not handled and inKey == 'KEY_ESCAPE':
Layout.Inst().PopDialogue()
handled = True

return handled

class DRRestoreSelection(Dialogue):

def __init__(self, date_choices, vdi_uuid, sr_uuid):
def __init__(self, date_choices, vdi_uuid, sr_uuid, legacy=False):
Dialogue.__init__(self)

choices = []
self.vdi_uuid = vdi_uuid
self.sr_uuid = sr_uuid
self.legacy = legacy
self.date_choices = date_choices.splitlines()
index = 0
for choice in self.date_choices:
Expand Down Expand Up @@ -86,14 +165,19 @@ def HandleMethodChoice(self, inChoice, dryRun):
Layout.Inst().PushDialogue(InfoDialogue(Lang("Internal Error, unexpected choice: " + inChoice)))
else:
chosen_mode = inChoice
if dryRun:
dry_flag="-n "
else:
dry_flag=""
Layout.Inst().TransientBanner(Lang("Restoring VM Metadata. This may take a few minutes..."))
command = "%s/xe-restore-metadata -y %s -u %s -x %s -d %s -m %s" % (Config.Inst().HelperPath(), dry_flag, self.sr_uuid, self.vdi_uuid, self.chosen_date, chosen_mode)
status, output = commands.getstatusoutput(command)
status = os.WEXITSTATUS(status)
command = ["%s/xe-restore-metadata" % (Config.Inst().HelperPath(),), "-y", "-u", self.sr_uuid, "-x", self.vdi_uuid, "-d", self.chosen_date, "-m", chosen_mode]
if dryRun:
command.append("-n")
if self.legacy:
command.append("-o")

cmd = subprocess.Popen(command,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
universal_newlines = True)
output, _ = cmd.communicate()
status = cmd.returncode
Layout.Inst().PopDialogue()
if status == 0:
Layout.Inst().PushDialogue(InfoDialogue(Lang("Metadata Restore Succeeded: ") + output))
Expand Down Expand Up @@ -137,40 +221,54 @@ def __init__(self):
}
SRDialogue.__init__(self) # Must fill in self.custom before calling __init__

def _searchForVDI(self, sr_uuid, legacy=False):
# probe for the restore VDI UUID
command = ["%s/xe-restore-metadata" % (Config.Inst().HelperPath(),), "-p", "-u", sr_uuid]
if legacy:
command.append("-o")
cmd = subprocess.Popen(command,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
universal_newlines = True)
output, errput = cmd.communicate()
status = cmd.returncode
if status != 0:
raise Exception("(%s,%s)" % (output,errput))
if len(output) == 0:
raise Exception(errput)
return output

def _earlierConfirmHandler(self, inYesNo, sr_uuid):
if inYesNo == 'y':
Layout.Inst().TransientBanner(Lang("Searching for backup VDI...\n\nCtrl-C to abort"))
try:
vdi_uuids = [v.strip() for v in self._searchForVDI(sr_uuid, legacy=True).splitlines()]
if len(vdi_uuids) == 1:
_listBackups(sr_uuid, vdi_uuids[0], legacy=True)
else:
Layout.Inst().PushDialogue(DRRestoreVDISelection(sr_uuid, vdi_uuids))
return
except Exception as e:
Layout.Inst().PushDialogue(InfoDialogue( Lang("Metadata Restore failed: ")+Lang(e)))
else:
Layout.Inst().PushDialogue(InfoDialogue( Lang("Metadata Restore failed: a backup VDI could not be found")))
Data.Inst().Update()

def DoAction(self, inSR):
Layout.Inst().PopDialogue()
Layout.Inst().TransientBanner(Lang("Searching for backup VDI...\n\nCtrl-C to abort"))
sr_uuid = inSR['uuid']
try:
# probe for the restore VDI UUID
command = "%s/xe-restore-metadata -p -u %s" % (Config.Inst().HelperPath(), sr_uuid)
cmd = subprocess.Popen(command,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
shell = True)
output = "".join(cmd.stdout).strip()
errput = "".join(cmd.stderr).strip()
status = cmd.wait()
if status != 0:
raise Exception("(%s,%s)" % (output,errput))
if len(output) == 0:
raise Exception(errput)
vdi_uuid = output

# list the available backups
Layout.Inst().TransientBanner(Lang("Found VDI, retrieving available backups..."))
command = "%s/xe-restore-metadata -l -u %s -x %s" % (Config.Inst().HelperPath(), sr_uuid, vdi_uuid)
cmd = subprocess.Popen(command,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
shell = True)
output = "".join(cmd.stdout).strip()
errput = "".join(cmd.stderr).strip()
status = cmd.wait()
if status != 0:
raise Exception("(%s,%s)" % (output,errput))
Layout.Inst().PushDialogue(DRRestoreSelection(output, vdi_uuid, sr_uuid))
except Exception, e:
try:
vdi_uuid = self._searchForVDI(sr_uuid).strip()
except Exception as e:
# We could not uniquely identify the required VDI, ask the user if they want to check for legacy ones
message = Lang("A backup VDI could not be positively identified. Do you wish to scan for backup VDIs created with earlier versions (Warning: this operation should only be performed if you trust the contents of all VDIs in this storage repository)?")
Layout.Inst().PushDialogue(QuestionDialogue(message, lambda x: self._earlierConfirmHandler(x, sr_uuid)))
return

_listBackups(sr_uuid, vdi_uuid)
except Exception as e:
Layout.Inst().PushDialogue(InfoDialogue( Lang("Metadata Restore failed: ")+Lang(e)))
Data.Inst().Update()

Expand Down

0 comments on commit 57d7a63

Please sign in to comment.