On commence par écrire la fonction qui fait le produit de deux monômes.

let produit_monomes {coeff=c1 ; degre=d1} {coeff=c2 ; degre=d2} = {coeff=c1*c2 ; degre=d1+d2}

En remarquant que le produit de deux monômes non-nuls est encore un monôme non-nul, on peut simplement itérer une fois avec List.map et obtenir le polynôme produit d'un monôme non-nul et d'un polynôme.

let produit_monome m p = List.map (fun mp -> produit_monomes m mp) p

Voire même en utilisant l'application partielle :
let produit_monome m p = List.map (produit_monomes m) p

En effet comme l'indique son type monome -> monome -> monome, à lire comme monome -> (monome -> monome), la fonction produit_monomes prend un monôme en argument et renvoie une fonction qui prend un monome en argument.

On termine en itérant encore une fois, à l'aide de List.fold_right.

let produit p1 p2 =
  List.fold_right
    (fun m1 r -> somme (produit_monome m1 p2) r)
    p1 []

On peut noter que, puisque somme est associative, on peut aussi employer List.fold_left.

let produit p1 p2 =
  List.fold_left
    (fun r m1 -> somme (produit_monome m1 p2) r)
    [] p1

Culture
Alors fold_right ou fold_left ? En ce qui me concerne, je me contente de me souvenir que l'on peut facilement copier une liste avec fold_right et facilement inverser une liste avec fold_left.

let copy l = List.fold_right (fun x r -> x::r) l []

let rev l = List.fold_left (fun r x -> x::r) [] l

Il y a une certaine logique dans tout ça, notamment dans l'ordre des arguments. En cas de choix possible (opération associative et commutative par exemple), la préférence va plutôt à fold_left qui est récursive terminale.