diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/CopyHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/CopyHandler.php
new file mode 100644
index 00000000000..5e4d3e39161
--- /dev/null
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/CopyHandler.php
@@ -0,0 +1,173 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Catalog\Model\Product\Gallery;
+
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Product\Gallery;
+use Magento\Eav\Model\ResourceModel\AttributeValue;
+use Magento\Framework\EntityManager\EntityMetadata;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+use Magento\Framework\Serialize\Serializer\Json;
+
+/**
+ * Copy gallery data from one product to another
+ */
+class CopyHandler implements ExtensionInterface
+{
+    /**
+     * @var EntityMetadata
+     */
+    private $metadata;
+
+    /**
+     * @var Gallery
+     */
+    private $galleryResourceModel;
+
+    /**
+     * @var ProductAttributeRepositoryInterface
+     */
+    private $attributeRepository;
+
+    /**
+     * @var AttributeValue
+     */
+    private $attributeValue;
+
+    /**
+     * @var Json
+     */
+    private $json;
+
+    /**
+     * @var ProductAttributeInterface
+     */
+    private $attribute;
+
+    /**
+     * @param MetadataPool $metadataPool
+     * @param Gallery $galleryResourceModel
+     * @param ProductAttributeRepositoryInterface $attributeRepository
+     * @param AttributeValue $attributeValue
+     * @param Json $json
+     */
+    public function __construct(
+        MetadataPool $metadataPool,
+        Gallery $galleryResourceModel,
+        ProductAttributeRepositoryInterface $attributeRepository,
+        AttributeValue $attributeValue,
+        Json $json
+    ) {
+        $this->metadata = $metadataPool->getMetadata(ProductInterface::class);
+        $this->galleryResourceModel = $galleryResourceModel;
+        $this->attributeRepository = $attributeRepository;
+        $this->attributeValue = $attributeValue;
+        $this->json = $json;
+    }
+
+    /**
+     * Copy gallery data from one product to another
+     *
+     * @param Product $product
+     * @param array $arguments
+     * @return void
+     */
+    public function execute($product, $arguments = []): void
+    {
+        $fromId = (int) $arguments['original_link_id'];
+        $toId = $product->getData($this->metadata->getLinkField());
+        $attributeId = $this->getAttribute()->getAttributeId();
+        $valueIdMap = $this->galleryResourceModel->duplicate($attributeId, [], $fromId, $toId);
+        $gallery = $this->getMediaGalleryCollection($product);
+
+        if (!empty($gallery['images'])) {
+            $images = [];
+            foreach ($gallery['images'] as $key => $image) {
+                $valueId = $image['value_id'] ?? null;
+                $newKey = $key;
+                if ($valueId !== null) {
+                    $newValueId = $valueId;
+                    if (isset($valueIdMap[$valueId])) {
+                        $newValueId = $valueIdMap[$valueId];
+                    }
+                    if (((int) $valueId) === $key) {
+                        $newKey = $newValueId;
+                    }
+                    $image['value_id'] = $newValueId;
+                }
+                $images[$newKey] = $image;
+            }
+            $gallery['images'] = $images;
+            $attrCode = $this->getAttribute()->getAttributeCode();
+            $product->setData($attrCode, $gallery);
+        }
+
+        //Copy media attribute values from one product to another
+        if (isset($arguments['media_attribute_codes'])) {
+            $values = $this->attributeValue->getValues(
+                ProductInterface::class,
+                $fromId,
+                $arguments['media_attribute_codes']
+            );
+            if ($values) {
+                foreach (array_keys($values) as $key) {
+                    $values[$key][$this->metadata->getLinkField()] = $product->getData($this->metadata->getLinkField());
+                    unset($values[$key]['value_id']);
+                }
+                $this->attributeValue->insertValues(
+                    ProductInterface::class,
+                    $values
+                );
+            }
+        }
+    }
+
+    /**
+     * Get product media gallery collection
+     *
+     * @param Product $product
+     * @return array
+     */
+    private function getMediaGalleryCollection(Product $product): array
+    {
+        $attrCode = $this->getAttribute()->getAttributeCode();
+        $value = $product->getData($attrCode);
+
+        if (is_array($value) && isset($value['images'])) {
+            if (!is_array($value['images']) && strlen($value['images']) > 0) {
+                $value['images'] = $this->json->unserialize($value['images']);
+            }
+
+            if (!is_array($value['images'])) {
+                $value['images'] = [];
+            }
+        }
+
+        return $value;
+    }
+
+    /**
+     * Returns media gallery attribute instance
+     *
+     * @return ProductAttributeInterface
+     */
+    private function getAttribute(): ProductAttributeInterface
+    {
+        if (!$this->attribute) {
+            $this->attribute = $this->attributeRepository->get(
+                ProductInterface::MEDIA_GALLERY
+            );
+        }
+
+        return $this->attribute;
+    }
+}
diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php
index 225a3a4c44a..5fefcf995e0 100644
--- a/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/CreateHandler.php
@@ -596,10 +596,21 @@ class CreateHandler implements ExtensionInterface
         $canRemoveImage = true;
         $gallery = $this->getImagesForAllStores($product);
         $storeId = $product->getStoreId();
+        $storeIds = [];
+        $storeIds[] = 0;
+        $websiteIds = array_map('intval', $product->getWebsiteIds() ?? []);
+        foreach ($this->storeManager->getStores() as $store) {
+            if (in_array((int) $store->getWebsiteId(), $websiteIds, true)) {
+                $storeIds[] = (int) $store->getId();
+            }
+        }
 
         if (!empty($gallery)) {
             foreach ($gallery as $image) {
-                if ($image['filepath'] === $imageFile && (int) $image['store_id'] !== $storeId) {
+                if (in_array((int) $image['store_id'], $storeIds)
+                    && $image['filepath'] === $imageFile
+                    && (int) $image['store_id'] !== $storeId
+                ) {
                     $canRemoveImage = false;
                 }
             }
diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/DeleteHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/DeleteHandler.php
new file mode 100644
index 00000000000..16adccb29b2
--- /dev/null
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/DeleteHandler.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Catalog\Model\Product\Gallery;
+
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ResourceModel\Product\Gallery;
+use Magento\Eav\Model\ResourceModel\AttributeValue;
+use Magento\Framework\EntityManager\EntityMetadata;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+
+/**
+ * Delete all media gallery records for provided product
+ */
+class DeleteHandler implements ExtensionInterface
+{
+    /**
+     * @var EntityMetadata
+     */
+    private $metadata;
+
+    /**
+     * @var Gallery
+     */
+    private $galleryResourceModel;
+
+    /**
+     * @var AttributeValue
+     */
+    private $attributeValue;
+
+    /**
+     * @param MetadataPool $metadataPool
+     * @param Gallery $galleryResourceModel
+     * @param AttributeValue $attributeValue
+     */
+    public function __construct(
+        MetadataPool $metadataPool,
+        Gallery $galleryResourceModel,
+        AttributeValue $attributeValue
+    ) {
+        $this->metadata = $metadataPool->getMetadata(ProductInterface::class);
+        $this->galleryResourceModel = $galleryResourceModel;
+        $this->attributeValue = $attributeValue;
+    }
+
+    /**
+     * Delete all media gallery records for provided product
+     *
+     * @param Product $product
+     * @param array $arguments
+     * @return void
+     */
+    public function execute($product, $arguments = []): void
+    {
+        $valuesId = $this->getMediaGalleryValuesId($product);
+        if ($valuesId) {
+            $this->galleryResourceModel->deleteGallery($valuesId);
+        }
+        if (isset($arguments['media_attribute_codes'])) {
+            $values = $this->attributeValue->getValues(
+                ProductInterface::class,
+                (int) $product->getData($this->metadata->getLinkField()),
+                $arguments['media_attribute_codes']
+            );
+            if ($values) {
+                $this->attributeValue->deleteValues(
+                    ProductInterface::class,
+                    $values
+                );
+            }
+        }
+    }
+
+    /**
+     * Get product media gallery values IDs
+     *
+     * @param Product $product
+     * @return array
+     */
+    private function getMediaGalleryValuesId(Product $product): array
+    {
+        $connection = $this->galleryResourceModel->getConnection();
+        $select = $connection->select()
+            ->from($this->galleryResourceModel->getTable(Gallery::GALLERY_VALUE_TO_ENTITY_TABLE))
+            ->where(
+                $this->metadata->getLinkField() . '=?',
+                $product->getData($this->metadata->getLinkField()),
+                \Zend_Db::INT_TYPE
+            );
+        return $connection->fetchCol($select);
+    }
+}
diff --git a/vendor/magento/module-catalog/Model/Product/Gallery/UpdateHandler.php b/vendor/magento/module-catalog/Model/Product/Gallery/UpdateHandler.php
index 049846ef364..8061422d842 100644
--- a/vendor/magento/module-catalog/Model/Product/Gallery/UpdateHandler.php
+++ b/vendor/magento/module-catalog/Model/Product/Gallery/UpdateHandler.php
@@ -5,17 +5,69 @@
  */
 namespace Magento\Catalog\Model\Product\Gallery;
 
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Media\Config;
 use Magento\Catalog\Model\ResourceModel\Product\Gallery;
-use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+use Magento\Eav\Model\ResourceModel\AttributeValue;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Json\Helper\Data;
+use Magento\MediaStorage\Helper\File\Storage\Database;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
 
 /**
  * Update handler for catalog product gallery.
  *
  * @api
  * @since 101.0.0
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
-class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
+class UpdateHandler extends CreateHandler
 {
+    /**
+     * @var AttributeValue
+     */
+    private $attributeValue;
+
+    /**
+     * @param MetadataPool $metadataPool
+     * @param ProductAttributeRepositoryInterface $attributeRepository
+     * @param Gallery $resourceModel
+     * @param Data $jsonHelper
+     * @param Config $mediaConfig
+     * @param Filesystem $filesystem
+     * @param Database $fileStorageDb
+     * @param StoreManagerInterface|null $storeManager
+     * @param AttributeValue|null $attributeValue
+     */
+    public function __construct(
+        MetadataPool $metadataPool,
+        ProductAttributeRepositoryInterface $attributeRepository,
+        Gallery $resourceModel,
+        Data $jsonHelper,
+        Config $mediaConfig,
+        Filesystem $filesystem,
+        Database $fileStorageDb,
+        StoreManagerInterface $storeManager = null,
+        ?AttributeValue $attributeValue = null
+    ) {
+        parent::__construct(
+            $metadataPool,
+            $attributeRepository,
+            $resourceModel,
+            $jsonHelper,
+            $mediaConfig,
+            $filesystem,
+            $fileStorageDb,
+            $storeManager
+        );
+        $this->attributeValue = $attributeValue ?: ObjectManager::getInstance()->get(AttributeValue::class);
+    }
+
     /**
      * @inheritdoc
      *
@@ -26,6 +78,7 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
         $filesToDelete = [];
         $recordsToDelete = [];
         $picturesInOtherStores = [];
+        $imagesToDelete = [];
 
         foreach ($this->resourceModel->getProductImages($product, $this->extractStoreIds($product)) as $image) {
             $picturesInOtherStores[$image['filepath']] = true;
@@ -38,6 +91,7 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
                         continue;
                     }
                     $recordsToDelete[] = $image['value_id'];
+                    $imagesToDelete[] = $image['file'];
                     $catalogPath = $this->mediaConfig->getBaseMediaPath();
                     $isFile = $this->mediaDirectory->isFile($catalogPath . $image['file']);
                     // only delete physical files if they are not used by any other products and if this file exist
@@ -48,8 +102,8 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
             }
         }
 
+        $this->deleteMediaAttributeValues($product, $imagesToDelete);
         $this->resourceModel->deleteGallery($recordsToDelete);
-
         $this->removeDeletedImages($filesToDelete);
     }
 
@@ -94,14 +148,14 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
     /**
      * Retrieve store ids from product.
      *
-     * @param \Magento\Catalog\Model\Product $product
+     * @param Product $product
      * @return array
      * @since 101.0.0
      */
     protected function extractStoreIds($product)
     {
         $storeIds = $product->getStoreIds();
-        $storeIds[] = \Magento\Store\Model\Store::DEFAULT_STORE_ID;
+        $storeIds[] = Store::DEFAULT_STORE_ID;
 
         // Removing current storeId.
         $storeIds = array_flip($storeIds);
@@ -125,5 +179,35 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
         foreach ($files as $filePath) {
             $this->mediaDirectory->delete($catalogPath . '/' . $filePath);
         }
+        return null;
+    }
+
+    /**
+     * Delete media attributes values for given images
+     *
+     * @param Product $product
+     * @param string[] $images
+     */
+    private function deleteMediaAttributeValues(Product $product, array $images): void
+    {
+        if ($images) {
+            $values = $this->attributeValue->getValues(
+                ProductInterface::class,
+                $product->getData($this->metadata->getLinkField()),
+                $this->mediaConfig->getMediaAttributeCodes()
+            );
+            $valuesToDelete = [];
+            foreach ($values as $value) {
+                if (in_array($value['value'], $images, true)) {
+                    $valuesToDelete[] = $value;
+                }
+            }
+            if ($valuesToDelete) {
+                $this->attributeValue->deleteValues(
+                    ProductInterface::class,
+                    $valuesToDelete
+                );
+            }
+        }
     }
 }
diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php b/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php
index a9741cd8e1e..d65cea7368a 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/Product/Gallery.php
@@ -442,6 +442,9 @@ class Gallery extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
         // Duplicate per store gallery values
         $select = $this->getConnection()->select()->from(
             $this->getTable(self::GALLERY_VALUE_TABLE)
+        )->where(
+            $linkField . ' = ?',
+            $originalProductId
         )->where(
             'value_id IN(?)',
             array_keys($valueIdMap)
diff --git a/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php b/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php
new file mode 100644
index 00000000000..66404c3ef38
--- /dev/null
+++ b/vendor/magento/module-eav/Model/ResourceModel/AttributeValue.php
@@ -0,0 +1,225 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\Eav\Model\ResourceModel;
+
+use Magento\Eav\Model\Config;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Select;
+use Magento\Framework\DB\Sql\UnionExpression;
+use Magento\Framework\EntityManager\MetadataPool;
+
+/**
+ * Entity attribute values resource
+ */
+class AttributeValue
+{
+    /**
+     * @var MetadataPool
+     */
+    private $metadataPool;
+    /**
+     * @var ResourceConnection
+     */
+    private $resourceConnection;
+    /**
+     * @var Config
+     */
+    private $config;
+
+    /**
+     * @param ResourceConnection $resourceConnection
+     * @param MetadataPool $metadataPool
+     * @param Config $config
+     */
+    public function __construct(
+        ResourceConnection $resourceConnection,
+        MetadataPool $metadataPool,
+        Config $config
+    ) {
+        $this->resourceConnection = $resourceConnection;
+        $this->metadataPool = $metadataPool;
+        $this->config = $config;
+    }
+
+    /**
+     * Get attribute values for given entity type, entity ID, attribute codes and store IDs
+     *
+     * @param string $entityType
+     * @param int $entityId
+     * @param string[] $attributeCodes
+     * @param int[] $storeIds
+     * @return array
+     */
+    public function getValues(
+        string $entityType,
+        int $entityId,
+        array $attributeCodes = [],
+        array $storeIds = []
+    ): array {
+        $metadata = $this->metadataPool->getMetadata($entityType);
+        $connection = $metadata->getEntityConnection();
+        $selects = [];
+        $attributeTables = [];
+        $attributes = [];
+        $allAttributes = $this->getEntityAttributes($entityType);
+        $result = [];
+        if ($attributeCodes) {
+            foreach ($attributeCodes as $attributeCode) {
+                $attributes[$attributeCode] = $allAttributes[$attributeCode];
+            }
+        } else {
+            $attributes = $allAttributes;
+        }
+
+        foreach ($attributes as $attribute) {
+            if (!$attribute->isStatic()) {
+                $attributeTables[$attribute->getBackend()->getTable()][] = $attribute->getAttributeId();
+            }
+        }
+
+        if ($attributeTables) {
+            foreach ($attributeTables as $attributeTable => $attributeIds) {
+                $select = $connection->select()
+                    ->from(
+                        ['t' => $attributeTable],
+                        ['*']
+                    )
+                    ->where($metadata->getLinkField() . ' = ?', $entityId)
+                    ->where('attribute_id IN (?)', $attributeIds);
+                if (!empty($storeIds)) {
+                    $select->where(
+                        'store_id IN (?)',
+                        $storeIds
+                    );
+                }
+                $selects[] = $select;
+            }
+
+            if (count($selects) > 1) {
+                $select = $connection->select();
+                $select->from(['u' => new UnionExpression($selects, Select::SQL_UNION_ALL, '( %s )')]);
+            } else {
+                $select = reset($selects);
+            }
+
+            $result = $connection->fetchAll($select);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Delete attribute values
+     *
+     * @param string $entityType
+     * @param array[] $values
+     * Format:
+     * array(
+     *      0 => array(
+     *          value_id => 1,
+     *          attribute_id => 11
+     *      ),
+     *      1 => array(
+     *          value_id => 2,
+     *          attribute_id => 22
+     *      )
+     * )
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    public function deleteValues(string $entityType, array $values): void
+    {
+        $metadata = $this->metadataPool->getMetadata($entityType);
+        $connection = $metadata->getEntityConnection();
+        $attributeTables = [];
+        $allAttributes = [];
+
+        foreach ($this->getEntityAttributes($entityType) as $attribute) {
+            $allAttributes[(int) $attribute->getAttributeId()] = $attribute;
+        }
+
+        foreach ($values as $value) {
+            $attribute = $allAttributes[(int) $value['attribute_id']] ?? null;
+            if ($attribute && !$attribute->isStatic()) {
+                $attributeTables[$attribute->getBackend()->getTable()][] = (int) $value['value_id'];
+            }
+        }
+
+        foreach ($attributeTables as $attributeTable => $valueIds) {
+            $connection->delete(
+                $attributeTable,
+                [
+                    'value_id IN (?)' => $valueIds
+                ]
+            );
+        }
+    }
+
+    /**
+     * Insert attribute values
+     *
+     * @param string $entityType
+     * @param array[] $values
+     * Format:
+     * array(
+     *      0 => array(
+     *          attribute_id => 11,
+     *          value => 'some long text',
+     *          ...
+     *      ),
+     *      1 => array(
+     *          attribute_id => 22,
+     *          value => 'some short text',
+     *          ...
+     *      )
+     * )
+     */
+    public function insertValues(string $entityType, array $values): void
+    {
+        $metadata = $this->metadataPool->getMetadata($entityType);
+        $connection = $metadata->getEntityConnection();
+        $attributeTables = [];
+        $allAttributes = [];
+
+        foreach ($this->getEntityAttributes($entityType) as $attribute) {
+            $allAttributes[(int) $attribute->getAttributeId()] = $attribute;
+        }
+
+        foreach ($values as $value) {
+            $attribute = $allAttributes[(int) $value['attribute_id']] ?? null;
+            if ($attribute && !$attribute->isStatic()) {
+                $columns = array_keys($value);
+                $columnsHash = implode(',', $columns);
+                $attributeTable = $attribute->getBackend()->getTable();
+                $attributeTables[$attributeTable][$columnsHash][] = array_values($value);
+            }
+        }
+
+        foreach ($attributeTables as $table => $tableData) {
+            foreach ($tableData as $columns => $data) {
+                $connection->insertArray(
+                    $table,
+                    explode(',', $columns),
+                    $data
+                );
+            }
+        }
+    }
+
+    /**
+     * Get attribute of given entity type
+     *
+     * @param string $entityType
+     * @return array
+     */
+    private function getEntityAttributes(string $entityType): array
+    {
+        $metadata = $this->metadataPool->getMetadata($entityType);
+        $eavEntityType = $metadata->getEavEntityType();
+        return null === $eavEntityType ? [] : $this->config->getEntityAttributes($eavEntityType);
+    }
+}
