Repository: operator-framework/operator-lifecycle-manager
Pull request #3167 (2024-01-31 23:00:39)

build(deps): bump github.com/opencontainers/runc from 1.1.9 to 1.1.12
Bumps [github.com/opencontainers/runc](https://github.com/opencontainers/runc) from 1.1.9 to 1.1.12.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/opencontainers/runc/releases">github.com/opencontainers/runc's releases</a>.</em></p>
<blockquote>
<h2>runc 1.1.12 -- &quot;Now you're thinking with Portals™!&quot;</h2>
<p>This is the twelfth patch release in the 1.1.z release branch of runc.
It fixes a high-severity container breakout vulnerability involving
leaked file descriptors, and users are strongly encouraged to update as
soon as possible.</p>
<ul>
<li>
<p>Fix <a href="https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv">CVE-2024-21626</a>, a container breakout attack that took advantage of
a file descriptor that was leaked internally within runc (but never
leaked to the container process).</p>
<p>In addition to fixing the leak, several strict hardening measures were
added to ensure that future internal leaks could not be used to break
out in this manner again.</p>
<p>Based on our research, while no other container runtime had a similar
leak, none had any of the hardening steps we've introduced (and some
runtimes would not check for any file descriptors that a calling
process may have leaked to them, allowing for container breakouts due
to basic user error).</p>
</li>
</ul>
<h3>Static Linking Notices</h3>
<p>The <code>runc</code> binary distributed with this release are <em>statically linked</em> with
the following <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">GNU LGPL-2.1</a> licensed libraries, with <code>runc</code> acting
as a &quot;work that uses the Library&quot;:</p>
<ul>
<li><a href="https://github.com/seccomp/libseccomp">libseccomp</a></li>
</ul>
<p>The versions of these libraries were not modified from their upstream versions,
but in order to comply with the LGPL-2.1 (§6(a)), we have attached the
complete source code for those libraries which (when combined with the attached
runc source code) may be used to exercise your rights under the LGPL-2.1.</p>
<p>However we strongly suggest that you make use of your distribution's packages
or download them from the authoritative upstream sources, especially since
these libraries are related to the security of your containers.</p>
<!-- raw HTML omitted -->
<p>Thanks to all of the contributors who made this release possible:</p>
<ul>
<li>Aleksa Sarai <a href="mailto:cyphar@cyphar.com">cyphar@cyphar.com</a></li>
<li>hang.jiang <a href="mailto:hang.jiang@daocloud.io">hang.jiang@daocloud.io</a></li>
<li>lfbzhm <a href="mailto:lifubang@acmcoder.com">lifubang@acmcoder.com</a></li>
</ul>
<p>Signed-off-by: Aleksa Sarai <a href="mailto:cyphar@cyphar.com">cyphar@cyphar.com</a></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/opencontainers/runc/blob/v1.1.12/CHANGELOG.md">github.com/opencontainers/runc's changelog</a>.</em></p>
<blockquote>
<h2>[1.1.12] - 2024-01-31</h2>
<blockquote>
<p>Now you're thinking with Portals™!</p>
</blockquote>
<h3>Security</h3>
<ul>
<li>Fix <a href="https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv">CVE-2024-21626</a>, a container breakout attack that took
advantage of a file descriptor that was leaked internally within runc (but
never leaked to the container process). In addition to fixing the leak,
several strict hardening measures were added to ensure that future internal
leaks could not be used to break out in this manner again. Based on our
research, while no other container runtime had a similar leak, none had any
of the hardening steps we've introduced (and some runtimes would not check
for any file descriptors that a calling process may have leaked to them,
allowing for container breakouts due to basic user error).</li>
</ul>
<h2>[1.1.11] - 2024-01-01</h2>
<blockquote>
<p>Happy New Year!</p>
</blockquote>
<h3>Fixed</h3>
<ul>
<li>Fix several issues with userns path handling. (<a href="https://redirect.github.com/opencontainers/runc/issues/4122">#4122</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4124">#4124</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4134">#4134</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4144">#4144</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Support memory.peak and memory.swap.peak in cgroups v2.
Add <code>swapOnlyUsage</code> in <code>MemoryStats</code>. This field reports swap-only usage.
For cgroupv1, <code>Usage</code> and <code>Failcnt</code> are set by subtracting memory usage
from memory+swap usage. For cgroupv2, <code>Usage</code>, <code>Limit</code>, and <code>MaxUsage</code>
are set. (<a href="https://redirect.github.com/opencontainers/runc/issues/4000">#4000</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4010">#4010</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4131">#4131</a>)</li>
<li>build(deps): bump github.com/cyphar/filepath-securejoin. (<a href="https://redirect.github.com/opencontainers/runc/issues/4140">#4140</a>)</li>
</ul>
<h2>[1.1.10] - 2023-10-31</h2>
<blockquote>
<p>Śruba, przykręcona we śnie, nie zmieni sytuacji, jaka panuje na jawie.</p>
</blockquote>
<h3>Added</h3>
<ul>
<li>Support for <code>hugetlb.&lt;pagesize&gt;.rsvd</code> limiting and accounting. Fixes the
issue of postres failing when hugepage limits are set. (<a href="https://redirect.github.com/opencontainers/runc/issues/3859">#3859</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4077">#4077</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fixed permissions of a newly created directories to not depend on the value
of umask in tmpcopyup feature implementation. (<a href="https://redirect.github.com/opencontainers/runc/issues/3991">#3991</a>, <a href="https://redirect.github.com/opencontainers/runc/issues/4060">#4060</a>)</li>
<li>libcontainer: cgroup v1 GetStats now ignores missing <code>kmem.limit_in_bytes</code>
(fixes the compatibility with Linux kernel 6.1+). (<a href="https://redirect.github.com/opencontainers/runc/issues/4028">#4028</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/opencontainers/runc/commit/51d5e94601ceffbbd85688df1c928ecccbfa4685"><code>51d5e94</code></a> VERSION: release 1.1.12</li>
<li><a href="https://github.com/opencontainers/runc/commit/2a4ed3e75b9e80d93d1836a9c4c1ebfa2b78870e"><code>2a4ed3e</code></a> merge 1.1-ghsa-xr7r-f8xq-vfvv into release-1.1</li>
<li><a href="https://github.com/opencontainers/runc/commit/e9665f4d606b64bf9c4652ab2510da368bfbd951"><code>e9665f4</code></a> init: don't special-case logrus fds</li>
<li><a href="https://github.com/opencontainers/runc/commit/683ad2ff3b01fb142ece7a8b3829de17150cf688"><code>683ad2f</code></a> libcontainer: mark all non-stdio fds O_CLOEXEC before spawning init</li>
<li><a href="https://github.com/opencontainers/runc/commit/b6633f48a8c970433737b9be5bfe4f25d58a5aa7"><code>b6633f4</code></a> cgroup: plug leaks of /sys/fs/cgroup handle</li>
<li><a href="https://github.com/opencontainers/runc/commit/284ba3057e428f8d6c7afcc3b0ac752e525957df"><code>284ba30</code></a> init: close internal fds before execve</li>
<li><a href="https://github.com/opencontainers/runc/commit/fbe3eed1e568a376f371d2ced1b4ac16b7d7adde"><code>fbe3eed</code></a> setns init: do explicit lookup of execve argument early</li>
<li><a href="https://github.com/opencontainers/runc/commit/0994249a5ec4e363bfcf9af58a87a722e9a3a31b"><code>0994249</code></a> init: verify after chdir that cwd is inside the container</li>
<li><a href="https://github.com/opencontainers/runc/commit/506552a88bd3455e80a9b3829568e94ec0160309"><code>506552a</code></a> Fix File to Close</li>
<li><a href="https://github.com/opencontainers/runc/commit/099ff69336840fecf3fc0ab13aab4c3aded640c3"><code>099ff69</code></a> merge <a href="https://redirect.github.com/opencontainers/runc/issues/4177">#4177</a> into opencontainers/runc:release-1.1</li>
<li>Additional commits viewable in <a href="https://github.com/opencontainers/runc/compare/v1.1.9...v1.1.12">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/opencontainers/runc&package-manager=go_modules&previous-version=1.1.9&new-version=1.1.12)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/operator-framework/operator-lifecycle-manager/network/alerts).

</details>

openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/3167#" title="Author self-approved">dependabot[bot]</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign [joelanford](https://github.com/joelanford) for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["joelanford"]} -->

Patch:
diff --git a/OWNERS b/OWNERS
index 07328cfed..bbce1c48f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -9,7 +9,6 @@ approvers:
   - grokspawn
   - joelanford
   - tmshort
-  - ncdc
 # review == this code is good /lgtm
 reviewers:
   - kevinrizza
@@ -27,4 +26,3 @@ reviewers:
   - dtfranz
   - tmshort
   - stevekuznetsov
-  - ncdc
diff --git a/go.mod b/go.mod
index 0f7e4b61e..413b0044f 100644
--- a/go.mod
+++ b/go.mod
@@ -175,7 +175,7 @@ require (
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
-	github.com/opencontainers/runc v1.1.9 // indirect
+	github.com/opencontainers/runc v1.1.12 // indirect
 	github.com/opencontainers/runtime-spec v1.1.0 // indirect
 	github.com/pbnjay/strptime v0.0.0-20140226051138-5c05b0d668c9 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
diff --git a/go.sum b/go.sum
index 3bce0d0ac..caa7a4422 100644
--- a/go.sum
+++ b/go.sum
@@ -664,8 +664,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
 github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
-github.com/opencontainers/runc v1.1.9 h1:XR0VIHTGce5eWPkaPesqTBrhW2yAcaraWfsEalNwQLM=
-github.com/opencontainers/runc v1.1.9/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
+github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
+github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
 github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
 github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/openshift/api v0.0.0-20221021112143-4226c2167e40 h1:PxjGCA72RtsdHWToZLkjjeWm7WXXx4cuv0u4gtvLbrk=
diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go
index 53b76c627..43822935e 100644
--- a/pkg/controller/operators/catalog/operator.go
+++ b/pkg/controller/operators/catalog/operator.go
@@ -186,7 +186,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
 		return nil, err
 	}
 
-	canFilter, err := labeller.Validate(ctx, logger, metadataClient, crClient)
+	canFilter, err := labeller.Validate(ctx, logger, metadataClient, crClient, labeller.IdentityCatalogOperator)
 	if err != nil {
 		return nil, err
 	}
@@ -541,6 +541,49 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
 		return nil, err
 	}
 
+	{
+		gvr := servicesgvk
+		informer := serviceInformer.Informer()
+
+		logger := op.logger.WithFields(logrus.Fields{"gvr": gvr.String()})
+		logger.Info("registering owner reference fixer")
+
+		queue := workqueue.NewRateLimitingQueueWithConfig(workqueue.DefaultControllerRateLimiter(), workqueue.RateLimitingQueueConfig{
+			Name: gvr.String(),
+		})
+		queueInformer, err := queueinformer.NewQueueInformer(
+			ctx,
+			queueinformer.WithQueue(queue),
+			queueinformer.WithLogger(op.logger),
+			queueinformer.WithInformer(informer),
+			queueinformer.WithSyncer(queueinformer.LegacySyncHandler(func(obj interface{}) error {
+				service, ok := obj.(*corev1.Service)
+				if !ok {
+					err := fmt.Errorf("wrong type %T, expected %T: %#v", obj, new(*corev1.Service), obj)
+					logger.WithError(err).Error("casting failed")
+					return fmt.Errorf("casting failed: %w", err)
+				}
+
+				deduped := deduplicateOwnerReferences(service.OwnerReferences)
+				if len(deduped) != len(service.OwnerReferences) {
+					localCopy := service.DeepCopy()
+					localCopy.OwnerReferences = deduped
+					if _, err := op.opClient.KubernetesInterface().CoreV1().Services(service.Namespace).Update(ctx, localCopy, metav1.UpdateOptions{}); err != nil {
+						return err
+					}
+				}
+				return nil
+			}).ToSyncer()),
+		)
+		if err != nil {
+			return nil, err
+		}
+
+		if err := op.RegisterQueueInformer(queueInformer); err != nil {
+			return nil, err
+		}
+	}
+
 	// Wire Pods for CatalogSource
 	catsrcReq, err := labels.NewRequirement(reconciler.CatalogSourceLabelKey, selection.Exists, nil)
 	if err != nil {
@@ -1393,16 +1436,14 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error {
 	}
 
 	// Make sure that we no longer indicate unpacking progress
-	subs = o.setSubsCond(subs, v1alpha1.SubscriptionCondition{
-		Type:   v1alpha1.SubscriptionBundleUnpacking,
-		Status: corev1.ConditionFalse,
-	})
+	o.removeSubsCond(subs, v1alpha1.SubscriptionBundleUnpacking)
 
 	// Remove BundleUnpackFailed condition from subscriptions
 	o.removeSubsCond(subs, v1alpha1.SubscriptionBundleUnpackFailed)
 
 	// Remove resolutionfailed condition from subscriptions
 	o.removeSubsCond(subs, v1alpha1.SubscriptionResolutionFailed)
+
 	newSub := true
 	for _, updatedSub := range updatedSubs {
 		updatedSub.Status.RemoveConditions(v1alpha1.SubscriptionResolutionFailed)
@@ -1679,13 +1720,9 @@ func (o *Operator) setSubsCond(subs []*v1alpha1.Subscription, cond v1alpha1.Subs
 	return subList
 }
 
-// removeSubsCond will remove the condition to the subscription if it exists
-// Only return the list of updated subscriptions
-func (o *Operator) removeSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.SubscriptionConditionType) []*v1alpha1.Subscription {
-	var (
-		lastUpdated = o.now()
-	)
-	var subList []*v1alpha1.Subscription
+// removeSubsCond removes the given condition from all of the subscriptions in the input
+func (o *Operator) removeSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.SubscriptionConditionType) {
+	lastUpdated := o.now()
 	for _, sub := range subs {
 		cond := sub.Status.GetCondition(condType)
 		// if status is ConditionUnknown, the condition doesn't exist. Just skip
@@ -1694,9 +1731,7 @@ func (o *Operator) removeSubsCond(subs []*v1alpha1.Subscription, condType v1alph
 		}
 		sub.Status.LastUpdated = lastUpdated
 		sub.Status.RemoveConditions(condType)
-		subList = append(subList, sub)
 	}
-	return subList
 }
 
 func (o *Operator) updateSubscriptionStatuses(subs []*v1alpha1.Subscription) ([]*v1alpha1.Subscription, error) {
@@ -2860,3 +2895,7 @@ func (o *Operator) apiresourceFromGVK(gvk schema.GroupVersionKind) (metav1.APIRe
 	logger.Info("couldn't find GVK in api discovery")
 	return metav1.APIResource{}, olmerrors.GroupVersionKindNotFoundError{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}
 }
+
+func deduplicateOwnerReferences(refs []metav1.OwnerReference) []metav1.OwnerReference {
+	return sets.New(refs...).UnsortedList()
+}
diff --git a/pkg/controller/operators/labeller/filters.go b/pkg/controller/operators/labeller/filters.go
index 2167d3454..333bc2b9e 100644
--- a/pkg/controller/operators/labeller/filters.go
+++ b/pkg/controller/operators/labeller/filters.go
@@ -80,7 +80,39 @@ var filters = map[schema.GroupVersionResource]func(metav1.Object) bool{
 	},
 }
 
-func Validate(ctx context.Context, logger *logrus.Logger, metadataClient metadata.Interface, operatorClient operators.Interface) (bool, error) {
+type Identity string
+
+const (
+	IdentityCatalogOperator = "catalog-operator"
+	IdentityOLMOperator     = "olm-operator"
+)
+
+func gvrRelevant(identity Identity) func(schema.GroupVersionResource) bool {
+	switch identity {
+	case IdentityCatalogOperator:
+		return sets.New(
+			rbacv1.SchemeGroupVersion.WithResource("roles"),
+			rbacv1.SchemeGroupVersion.WithResource("rolebindings"),
+			corev1.SchemeGroupVersion.WithResource("serviceaccounts"),
+			corev1.SchemeGroupVersion.WithResource("services"),
+			corev1.SchemeGroupVersion.WithResource("pods"),
+			corev1.SchemeGroupVersion.WithResource("configmaps"),
+			batchv1.SchemeGroupVersion.WithResource("jobs"),
+			apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions"),
+		).Has
+	case IdentityOLMOperator:
+		return sets.New(
+			appsv1.SchemeGroupVersion.WithResource("deployments"),
+			rbacv1.SchemeGroupVersion.WithResource("clusterroles"),
+			rbacv1.SchemeGroupVersion.WithResource("clusterrolebindings"),
+			apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions"),
+		).Has
+	default:
+		panic("programmer error: invalid identity")
+	}
+}
+
+func Validate(ctx context.Context, logger *logrus.Logger, metadataClient metadata.Interface, operatorClient operators.Interface, identity Identity) (bool, error) {
 	okLock := sync.Mutex{}
 	ok := true
 	g, ctx := errgroup.WithContext(ctx)
@@ -125,6 +157,10 @@ func Validate(ctx context.Context, logger *logrus.Logger, metadataClient metadat
 	})
 
 	for gvr, filter := range allFilters {
+		if !gvrRelevant(identity)(gvr) {
+			logger.WithField("gvr", gvr.String()).Info("skipping irrelevant gvr")
+			continue
+		}
 		gvr, filter := gvr, filter
 		g.Go(func() error {
 			list, err := metadataClient.Resource(gvr).List(ctx, metav1.ListOptions{})
diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go
index 44d3645ac..ee75f84c0 100644
--- a/pkg/controller/operators/olm/operator.go
+++ b/pkg/controller/operators/olm/operator.go
@@ -183,7 +183,7 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
 		return nil, err
 	}
 
-	canFilter, err := labeller.Validate(ctx, config.logger, config.metadataClient, config.externalClient)
+	canFilter, err := labeller.Validate(ctx, config.logger, config.metadataClient, config.externalClient, labeller.IdentityOLMOperator)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/lib/ownerutil/util.go b/pkg/lib/ownerutil/util.go
index dafaeaf8b..ebdd73a31 100644
--- a/pkg/lib/ownerutil/util.go
+++ b/pkg/lib/ownerutil/util.go
@@ -167,20 +167,13 @@ func AddNonBlockingOwner(object metav1.Object, owner Owner) {
 		ownerRefs = []metav1.OwnerReference{}
 	}
 
-	// Infer TypeMeta for the target
-	if err := InferGroupVersionKind(owner); err != nil {
-		log.Warn(err.Error())
-	}
-	gvk := owner.GetObjectKind().GroupVersionKind()
-
+	nonBlockingOwner := NonBlockingOwner(owner)
 	for _, item := range ownerRefs {
-		if item.Kind == gvk.Kind {
-			if item.Name == owner.GetName() && item.UID == owner.GetUID() {
-				return
-			}
+		if item.Kind == nonBlockingOwner.Kind && item.Name == nonBlockingOwner.Name && item.UID == nonBlockingOwner.UID {
+			return
 		}
 	}
-	ownerRefs = append(ownerRefs, NonBlockingOwner(owner))
+	ownerRefs = append(ownerRefs, nonBlockingOwner)
 	object.SetOwnerReferences(ownerRefs)
 }
 
@@ -284,14 +277,20 @@ func AddOwner(object metav1.Object, owner Owner, blockOwnerDeletion, isControlle
 	}
 	gvk := owner.GetObjectKind().GroupVersionKind()
 	apiVersion, kind := gvk.ToAPIVersionAndKind()
-	ownerRefs = append(ownerRefs, metav1.OwnerReference{
+	ownerRef := metav1.OwnerReference{
 		APIVersion:         apiVersion,
 		Kind:               kind,
 		Name:               owner.GetName(),
 		UID:                owner.GetUID(),
 		BlockOwnerDeletion: &blockOwnerDeletion,
 		Controller:         &isController,
-	})
+	}
+	for _, ref := range ownerRefs {
+		if ref.Kind == ownerRef.Kind && ref.Name == ownerRef.Name && ref.UID == ownerRef.UID {
+			return
+		}
+	}
+	ownerRefs = append(ownerRefs, ownerRef)
 	object.SetOwnerReferences(ownerRefs)
 }
 
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 843d22eac..0aee124f2 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -694,7 +694,7 @@ github.com/opencontainers/go-digest
 ## explicit; go 1.18
 github.com/opencontainers/image-spec/specs-go
 github.com/opencontainers/image-spec/specs-go/v1
-# github.com/opencontainers/runc v1.1.9
+# github.com/opencontainers/runc v1.1.12
 ## explicit; go 1.17
 github.com/opencontainers/runc/libcontainer/user
 # github.com/opencontainers/runtime-spec v1.1.0


Test patch:
diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go
index bd906dcf4..27535f5c4 100644
--- a/pkg/controller/operators/catalog/operator_test.go
+++ b/pkg/controller/operators/catalog/operator_test.go
@@ -1261,13 +1261,6 @@ func TestSyncResolvingNamespace(t *testing.T) {
 					Status: v1alpha1.SubscriptionStatus{
 						CurrentCSV: "",
 						State:      "",
-						Conditions: []v1alpha1.SubscriptionCondition{
-							{
-								Type:   v1alpha1.SubscriptionBundleUnpacking,
-								Status: corev1.ConditionFalse,
-							},
-						},
-						LastUpdated: now,
 					},
 				},
 			},
@@ -1399,6 +1392,62 @@ func TestSyncResolvingNamespace(t *testing.T) {
 			},
 			wantErr: fmt.Errorf("some error"),
 		},
+		{
+			name: "HadErrorShouldClearError",
+			fields: fields{
+				clientOptions: []clientfake.Option{clientfake.WithSelfLinks(t)},
+				existingOLMObjs: []runtime.Object{
+					&v1alpha1.Subscription{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       v1alpha1.SubscriptionKind,
+							APIVersion: v1alpha1.SchemeGroupVersion.String(),
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "sub",
+							Namespace: testNamespace,
+						},
+						Spec: &v1alpha1.SubscriptionSpec{
+							CatalogSource:          "src",
+							CatalogSourceNamespace: testNamespace,
+						},
+						Status: v1alpha1.SubscriptionStatus{
+							InstalledCSV: "sub-csv",
+							State:        "AtLatestKnown",
+							Conditions: []v1alpha1.SubscriptionCondition{
+								{
+									Type:    v1alpha1.SubscriptionResolutionFailed,
+									Reason:  "ConstraintsNotSatisfiable",
+									Message: "constraints not satisfiable: no operators found from catalog src in namespace testNamespace referenced by subscrition sub, subscription sub exists",
+									Status:  corev1.ConditionTrue,
+								},
+							},
+						},
+					},
+				},
+				resolveErr: nil,
+			},
+			wantSubscriptions: []*v1alpha1.Subscription{
+				{
+					TypeMeta: metav1.TypeMeta{
+						Kind:       v1alpha1.SubscriptionKind,
+						APIVersion: v1alpha1.SchemeGroupVersion.String(),
+					},
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "sub",
+						Namespace: testNamespace,
+					},
+					Spec: &v1alpha1.SubscriptionSpec{
+						CatalogSource:          "src",
+						CatalogSourceNamespace: testNamespace,
+					},
+					Status: v1alpha1.SubscriptionStatus{
+						InstalledCSV: "sub-csv",
+						State:        "AtLatestKnown",
+						LastUpdated:  now,
+					},
+				},
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/pkg/controller/operators/catalog/ownerrefs_test.go b/pkg/controller/operators/catalog/ownerrefs_test.go
new file mode 100644
index 000000000..097a8409b
--- /dev/null
+++ b/pkg/controller/operators/catalog/ownerrefs_test.go
@@ -0,0 +1,42 @@
+package catalog
+
+import (
+	"testing"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestDedupe(t *testing.T) {
+	yes := true
+	refs := []metav1.OwnerReference{
+		{
+			APIVersion:         "apiversion",
+			Kind:               "kind",
+			Name:               "name",
+			UID:                "uid",
+			Controller:         &yes,
+			BlockOwnerDeletion: &yes,
+		},
+		{
+			APIVersion:         "apiversion",
+			Kind:               "kind",
+			Name:               "name",
+			UID:                "uid",
+			Controller:         &yes,
+			BlockOwnerDeletion: &yes,
+		},
+		{
+			APIVersion:         "apiversion",
+			Kind:               "kind",
+			Name:               "name",
+			UID:                "uid",
+			Controller:         &yes,
+			BlockOwnerDeletion: &yes,
+		},
+	}
+	deduped := deduplicateOwnerReferences(refs)
+	t.Logf("got %d deduped from %d", len(deduped), len(refs))
+	if len(deduped) == len(refs) {
+		t.Errorf("didn't dedupe: %#v", deduped)
+	}
+}
diff --git a/pkg/lib/testobj/runtime.go b/pkg/lib/testobj/runtime.go
index 70da4f892..6fd7c8b72 100644
--- a/pkg/lib/testobj/runtime.go
+++ b/pkg/lib/testobj/runtime.go
@@ -135,14 +135,21 @@ func WithOwner(owner, obj RuntimeMetaObject) RuntimeMetaObject {
 	out := obj.DeepCopyObject().(RuntimeMetaObject)
 	gvk := owner.GetObjectKind().GroupVersionKind()
 	apiVersion, kind := gvk.ToAPIVersionAndKind()
-	refs := append(out.GetOwnerReferences(), metav1.OwnerReference{
+
+	nonBlockingOwner := metav1.OwnerReference{
 		APIVersion:         apiVersion,
 		Kind:               kind,
 		Name:               owner.GetName(),
 		UID:                owner.GetUID(),
 		BlockOwnerDeletion: &dontBlockOwnerDeletion,
 		Controller:         &notController,
-	})
+	}
+	for _, item := range out.GetOwnerReferences() {
+		if item.Kind == nonBlockingOwner.Kind && item.Name == nonBlockingOwner.Name && item.UID == nonBlockingOwner.UID {
+			return out
+		}
+	}
+	refs := append(out.GetOwnerReferences(), nonBlockingOwner)
 	out.SetOwnerReferences(refs)
 
 	return out
diff --git a/test/e2e/crd_e2e_test.go b/test/e2e/crd_e2e_test.go
index 2cbfb30be..79091b6fa 100644
--- a/test/e2e/crd_e2e_test.go
+++ b/test/e2e/crd_e2e_test.go
@@ -3,6 +3,8 @@ package e2e
 import (
 	"context"
 	"fmt"
+	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
 	"time"
 
 	"github.com/blang/semver/v4"
@@ -30,7 +32,18 @@ var _ = Describe("CRD Versions", func() {
 	BeforeEach(func() {
 		c = ctx.Ctx().KubeClient()
 		crc = ctx.Ctx().OperatorClient()
-		generatedNamespace = SetupGeneratedTestNamespace(genName("crd-e2e-"))
+		namespace := genName("crd-e2e-")
+		og := operatorsv1.OperatorGroup{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      fmt.Sprintf("%s-operatorgroup", namespace),
+				Namespace: namespace,
+				Annotations: map[string]string{
+					// Reduce the bundle unpack timeout to ensure error states are reached quickly
+					bundle.BundleUnpackTimeoutAnnotationKey: "5s",
+				},
+			},
+		}
+		generatedNamespace = SetupGeneratedTestNamespaceWithOperatorGroup(namespace, og)
 	})
 
 	AfterEach(func() {
@@ -113,184 +126,6 @@ var _ = Describe("CRD Versions", func() {
 		GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
 		Expect(fetchedInstallPlan.Status.Phase).To(Equal(operatorsv1alpha1.InstallPlanPhaseComplete))
 	})
-
-	// issue:https://github.com/operator-framework/operator-lifecycle-manager/issues/2638
-	It("[FLAKE] blocks a CRD upgrade that could cause data loss", func() {
-		By("checking the storage versions in the existing CRD status and the spec of the new CRD")
-
-		mainPackageName := genName("nginx-update2-")
-		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
-		stableChannel := "stable"
-
-		crdPlural := genName("ins-")
-		crdName := crdPlural + ".cluster.com"
-		oldCRD := apiextensionsv1.CustomResourceDefinition{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: crdName,
-			},
-			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
-				Group: "cluster.com",
-				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
-					{
-						Name:    "v1alpha2",
-						Served:  true,
-						Storage: true,
-						Schema: &apiextensionsv1.CustomResourceValidation{
-							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
-								Type:        "object",
-								Description: "my crd schema",
-							},
-						},
-					},
-					{
-						Name:    "v2alpha1",
-						Served:  true,
-						Storage: false,
-						Schema: &apiextensionsv1.CustomResourceValidation{
-							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
-								Type:        "object",
-								Description: "my crd schema",
-							},
-						},
-					},
-				},
-				Names: apiextensionsv1.CustomResourceDefinitionNames{
-					Plural:   crdPlural,
-					Singular: crdPlural,
-					Kind:     crdPlural,
-					ListKind: "list" + crdPlural,
-				},
-				Scope: apiextensionsv1.NamespaceScoped,
-			},
-		}
-
-		alphaChannel := "alpha"
-		mainPackageAlpha := fmt.Sprintf("%s-alpha", mainPackageName)
-		newCRD := apiextensionsv1.CustomResourceDefinition{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: crdName,
-			},
-			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
-				Group: "cluster.com",
-				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
-					{
-						Name:    "v1alpha3",
-						Served:  true,
-						Storage: true,
-						Schema: &apiextensionsv1.CustomResourceValidation{
-							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
-								Type:        "object",
-								Description: "my crd schema",
-							},
-						},
-					},
-					{
-						Name:    "v2alpha2",
-						Served:  true,
-						Storage: false,
-						Schema: &apiextensionsv1.CustomResourceValidation{
-							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
-								Type:        "object",
-								Description: "my crd schema",
-							},
-						},
-					},
-				},
-				Names: apiextensionsv1.CustomResourceDefinitionNames{
-					Plural:   crdPlural,
-					Singular: crdPlural,
-					Kind:     crdPlural,
-					ListKind: "list" + crdPlural,
-				},
-				Scope: apiextensionsv1.NamespaceScoped,
-			},
-		}
-
-		oldCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{oldCRD}, nil, nil)
-		newCSV := newCSV(mainPackageAlpha, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.1.1"), []apiextensionsv1.CustomResourceDefinition{newCRD}, nil, nil)
-		mainCatalogName := genName("mock-ocs-main-update2-")
-		mainManifests := []registry.PackageManifest{
-			{
-				PackageName: mainPackageName,
-				Channels: []registry.PackageChannel{
-					{Name: stableChannel, CurrentCSVName: mainPackageStable},
-					{Name: alphaChannel, CurrentCSVName: mainPackageAlpha},
-				},
-				DefaultChannelName: stableChannel,
-			},
-		}
-
-		By("Create the catalog sources")
-		_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{oldCRD, newCRD}, []operatorsv1alpha1.ClusterServiceVersion{oldCSV, newCSV})
-		defer cleanupMainCatalogSource()
-		defer func() {
-			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), oldCSV.GetName(), metav1.DeleteOptions{})
-			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), newCSV.GetName(), metav1.DeleteOptions{})
-			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), oldCRD.GetName(), metav1.DeleteOptions{})
-			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), newCRD.GetName(), metav1.DeleteOptions{})
-		}()
-
-		By("Attempt to get the catalog source before creating install plan")
-		_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
-		Expect(err).ToNot(HaveOccurred())
-
-		subscriptionName := genName("sub-nginx-update2-")
-		subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
-		defer subscriptionCleanup()
-
-		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
-		Expect(err).ToNot(HaveOccurred())
-		Expect(subscription).ToNot(BeNil())
-		Expect(subscription.Status.InstallPlanRef).ToNot(Equal(nil))
-		Expect(oldCSV.GetName()).To(Equal(subscription.Status.CurrentCSV))
-
-		installPlanName := subscription.Status.InstallPlanRef.Name
-
-		By("Wait for InstallPlan to be status: Complete before checking resource presence")
-		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
-		Expect(err).ToNot(HaveOccurred())
-		GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
-		Expect(fetchedInstallPlan.Status.Phase).To(Equal(operatorsv1alpha1.InstallPlanPhaseComplete))
-
-		By("old CRD has been installed onto the cluster - now upgrade the subscription to point to the channel with the new CRD")
-		By("installing the new CSV should fail with a warning about data loss, since a storage version is missing in the new CRD")
-		By("use server-side apply to apply the update to the subscription point to the alpha channel")
-		Eventually(Apply(subscription, func(s *operatorsv1alpha1.Subscription) error {
-			s.Spec.Channel = alphaChannel
-			return nil
-		})).Should(Succeed())
-		ctx.Ctx().Logf("updated subscription to point to alpha channel")
-
-		checker := subscriptionStateAtLatestChecker()
-		subscriptionAtLatestWithDifferentInstallPlan := func(v *operatorsv1alpha1.Subscription) bool {
-			return checker(v) && v.Status.InstallPlanRef != nil && v.Status.InstallPlanRef.Name != fetchedInstallPlan.Name
-		}
-
-		By("fetch new subscription")
-		s, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionAtLatestWithDifferentInstallPlan)
-		Expect(err).ToNot(HaveOccurred())
-		Expect(s).ToNot(BeNil())
-		Expect(s.Status.InstallPlanRef).ToNot(Equal(nil))
-
-		By("Check the error on the installplan - should be related to data loss and the CRD upgrade missing a stored version")
-		Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
-			return crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), s.Status.InstallPlanRef.Name, metav1.GetOptions{})
-		}).Should(And(
-			WithTransform(
-				func(v *operatorsv1alpha1.InstallPlan) operatorsv1alpha1.InstallPlanPhase {
-					return v.Status.Phase
-				},
-				Equal(operatorsv1alpha1.InstallPlanPhaseFailed),
-			),
-			WithTransform(
-				func(v *operatorsv1alpha1.InstallPlan) string {
-					return v.Status.Conditions[len(v.Status.Conditions)-1].Message
-				},
-				ContainSubstring("risk of data loss"),
-			),
-		))
-	})
-
 	It("allows a CRD upgrade that doesn't cause data loss", func() {
 		By(`Create a CRD on cluster with v1alpha1 (storage)`)
 		By(`Update that CRD with v1alpha2 (storage), v1alpha1 (served)`)
@@ -461,7 +296,7 @@ var _ = Describe("CRD Versions", func() {
 		err = crc.OperatorsV1alpha1().Subscriptions(generatedNamespace.GetName()).Delete(context.TODO(), subscription.GetName(), metav1.DeleteOptions{})
 		Expect(err).ToNot(HaveOccurred(), "error deleting old subscription")
 		By("remove old csv")
-		crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), mainPackageStable, metav1.DeleteOptions{})
+		err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), mainPackageStable, metav1.DeleteOptions{})
 		Expect(err).ToNot(HaveOccurred(), "error deleting old subscription")
 
 		By("recreate subscription")
@@ -493,4 +328,181 @@ var _ = Describe("CRD Versions", func() {
 		}).Should(BeTrue())
 		GinkgoT().Log("manually reconciled potentially unsafe CRD upgrade")
 	})
+
+	It("blocks a CRD upgrade that could cause data loss", func() {
+		By("checking the storage versions in the existing CRD status and the spec of the new CRD")
+
+		mainPackageName := genName("nginx-update2-")
+		mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
+		stableChannel := "stable"
+
+		crdPlural := genName("ins-")
+		crdName := crdPlural + ".cluster.com"
+		oldCRD := apiextensionsv1.CustomResourceDefinition{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: crdName,
+			},
+			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
+				Group: "cluster.com",
+				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
+					{
+						Name:    "v1alpha2",
+						Served:  true,
+						Storage: true,
+						Schema: &apiextensionsv1.CustomResourceValidation{
+							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
+								Type:        "object",
+								Description: "my crd schema",
+							},
+						},
+					},
+					{
+						Name:    "v2alpha1",
+						Served:  true,
+						Storage: false,
+						Schema: &apiextensionsv1.CustomResourceValidation{
+							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
+								Type:        "object",
+								Description: "my crd schema",
+							},
+						},
+					},
+				},
+				Names: apiextensionsv1.CustomResourceDefinitionNames{
+					Plural:   crdPlural,
+					Singular: crdPlural,
+					Kind:     crdPlural,
+					ListKind: "list" + crdPlural,
+				},
+				Scope: apiextensionsv1.NamespaceScoped,
+			},
+		}
+
+		alphaChannel := "alpha"
+		mainPackageAlpha := fmt.Sprintf("%s-alpha", mainPackageName)
+		newCRD := apiextensionsv1.CustomResourceDefinition{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: crdName,
+			},
+			Spec: apiextensionsv1.CustomResourceDefinitionSpec{
+				Group: "cluster.com",
+				Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
+					{
+						Name:    "v1alpha3",
+						Served:  true,
+						Storage: true,
+						Schema: &apiextensionsv1.CustomResourceValidation{
+							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
+								Type:        "object",
+								Description: "my crd schema",
+							},
+						},
+					},
+					{
+						Name:    "v2alpha2",
+						Served:  true,
+						Storage: false,
+						Schema: &apiextensionsv1.CustomResourceValidation{
+							OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
+								Type:        "object",
+								Description: "my crd schema",
+							},
+						},
+					},
+				},
+				Names: apiextensionsv1.CustomResourceDefinitionNames{
+					Plural:   crdPlural,
+					Singular: crdPlural,
+					Kind:     crdPlural,
+					ListKind: "list" + crdPlural,
+				},
+				Scope: apiextensionsv1.NamespaceScoped,
+			},
+		}
+
+		oldCSV := newCSV(mainPackageStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensionsv1.CustomResourceDefinition{oldCRD}, nil, nil)
+		newCSV := newCSV(mainPackageAlpha, generatedNamespace.GetName(), mainPackageStable, semver.MustParse("0.1.1"), []apiextensionsv1.CustomResourceDefinition{newCRD}, nil, nil)
+		mainCatalogName := genName("mock-ocs-main-update2-")
+		mainManifests := []registry.PackageManifest{
+			{
+				PackageName: mainPackageName,
+				Channels: []registry.PackageChannel{
+					{Name: stableChannel, CurrentCSVName: mainPackageStable},
+					{Name: alphaChannel, CurrentCSVName: mainPackageAlpha},
+				},
+				DefaultChannelName: stableChannel,
+			},
+		}
+
+		By("Create the catalog sources")
+		_, cleanupMainCatalogSource := createInternalCatalogSource(c, crc, mainCatalogName, generatedNamespace.GetName(), mainManifests, []apiextensionsv1.CustomResourceDefinition{oldCRD, newCRD}, []operatorsv1alpha1.ClusterServiceVersion{oldCSV, newCSV})
+		defer cleanupMainCatalogSource()
+		defer func() {
+			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), oldCSV.GetName(), metav1.DeleteOptions{})
+			_ = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), newCSV.GetName(), metav1.DeleteOptions{})
+			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), oldCRD.GetName(), metav1.DeleteOptions{})
+			_ = c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), newCRD.GetName(), metav1.DeleteOptions{})
+		}()
+
+		By("Attempt to get the catalog source before creating install plan")
+		_, err := fetchCatalogSourceOnStatus(crc, mainCatalogName, generatedNamespace.GetName(), catalogSourceRegistryPodSynced())
+		Expect(err).ToNot(HaveOccurred())
+
+		subscriptionName := genName("sub-nginx-update2-")
+		subscriptionCleanup := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", operatorsv1alpha1.ApprovalAutomatic)
+		defer subscriptionCleanup()
+
+		subscription, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionHasInstallPlanChecker())
+		Expect(err).ToNot(HaveOccurred())
+		Expect(subscription).ToNot(BeNil())
+		Expect(subscription.Status.InstallPlanRef).ToNot(Equal(nil))
+		Expect(oldCSV.GetName()).To(Equal(subscription.Status.CurrentCSV))
+
+		installPlanName := subscription.Status.InstallPlanRef.Name
+
+		By("Wait for InstallPlan to be status: Complete before checking resource presence")
+		fetchedInstallPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, generatedNamespace.GetName(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseComplete))
+		Expect(err).ToNot(HaveOccurred())
+		GinkgoT().Logf("Install plan %s fetched with status %s", fetchedInstallPlan.GetName(), fetchedInstallPlan.Status.Phase)
+		Expect(fetchedInstallPlan.Status.Phase).To(Equal(operatorsv1alpha1.InstallPlanPhaseComplete))
+
+		By("old CRD has been installed onto the cluster - now upgrade the subscription to point to the channel with the new CRD")
+		By("installing the new CSV should fail with a warning about data loss, since a storage version is missing in the new CRD")
+		By("use server-side apply to apply the update to the subscription point to the alpha channel")
+		Eventually(Apply(subscription, func(s *operatorsv1alpha1.Subscription) error {
+			s.Spec.Channel = alphaChannel
+			return nil
+		})).Should(Succeed())
+		ctx.Ctx().Logf("updated subscription to point to alpha channel")
+
+		checker := subscriptionStateAtLatestChecker()
+		subscriptionAtLatestWithDifferentInstallPlan := func(v *operatorsv1alpha1.Subscription) bool {
+			return checker(v) && v.Status.InstallPlanRef != nil && v.Status.InstallPlanRef.Name != fetchedInstallPlan.Name
+		}
+
+		By("fetch new subscription")
+		s, err := fetchSubscription(crc, generatedNamespace.GetName(), subscriptionName, subscriptionAtLatestWithDifferentInstallPlan)
+		Expect(err).ToNot(HaveOccurred())
+		Expect(s).ToNot(BeNil())
+		Expect(s.Status.InstallPlanRef).ToNot(Equal(nil))
+
+		By("Check the error on the installplan - should be related to data loss and the CRD upgrade missing a stored version")
+		Eventually(func() (*operatorsv1alpha1.InstallPlan, error) {
+			return crc.OperatorsV1alpha1().InstallPlans(generatedNamespace.GetName()).Get(context.TODO(), s.Status.InstallPlanRef.Name, metav1.GetOptions{})
+			// the install plan retry time out is 60 seconds, so we should expect the install plan to be failed within 2 minutes
+		}).Within(2 * time.Minute).Should(And(
+			WithTransform(
+				func(v *operatorsv1alpha1.InstallPlan) operatorsv1alpha1.InstallPlanPhase {
+					return v.Status.Phase
+				},
+				Equal(operatorsv1alpha1.InstallPlanPhaseFailed),
+			),
+			WithTransform(
+				func(v *operatorsv1alpha1.InstallPlan) string {
+					return v.Status.Conditions[len(v.Status.Conditions)-1].Message
+				},
+				ContainSubstring("risk of data loss"),
+			),
+		))
+	})
 })
diff --git a/test/e2e/subscription_e2e_test.go b/test/e2e/subscription_e2e_test.go
index 6d499797f..8e66ced63 100644
--- a/test/e2e/subscription_e2e_test.go
+++ b/test/e2e/subscription_e2e_test.go
@@ -2445,7 +2445,7 @@ var _ = Describe("Subscription", func() {
 				},
 				5*time.Minute,
 				interval,
-			).Should(Equal(corev1.ConditionFalse))
+			).Should(Equal(corev1.ConditionUnknown))
 
 			By("verifying that the subscription is not reporting unpacking errors")
 			Eventually(
@@ -2806,7 +2806,7 @@ properties:
 				if err != nil {
 					return err
 				}
-				if cond := fetched.Status.GetCondition(v1alpha1.SubscriptionBundleUnpacking); cond.Status != corev1.ConditionFalse {
+				if cond := fetched.Status.GetCondition(v1alpha1.SubscriptionBundleUnpacking); cond.Status != corev1.ConditionUnknown {
 					return fmt.Errorf("subscription condition %s has unexpected value %s, expected %s", v1alpha1.SubscriptionBundleUnpacking, cond.Status, corev1.ConditionFalse)
 				}
 				if cond := fetched.Status.GetCondition(v1alpha1.SubscriptionBundleUnpackFailed); cond.Status != corev1.ConditionUnknown {